algokit-utils 5.0.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algokit_abi/__init__.py +9 -0
- algokit_abi/_arc32_to_arc56.py +242 -0
- algokit_abi/_arc56_serde.py +161 -0
- algokit_abi/abi.py +667 -0
- algokit_abi/arc32.py +210 -0
- algokit_abi/arc56.py +821 -0
- algokit_abi/py.typed +0 -0
- algokit_algo25/__init__.py +38 -0
- algokit_algo25/_encoding.py +46 -0
- algokit_algo25/_wordlist.py +2065 -0
- algokit_algo25/exceptions.py +29 -0
- algokit_algo25/mnemonic.py +128 -0
- algokit_algo25/py.typed +0 -0
- algokit_algod_client/__init__.py +10 -0
- algokit_algod_client/client.py +1585 -0
- algokit_algod_client/config.py +36 -0
- algokit_algod_client/exceptions.py +59 -0
- algokit_algod_client/models/__init__.py +229 -0
- algokit_algod_client/models/_account.py +150 -0
- algokit_algod_client/models/_account_application_response.py +25 -0
- algokit_algod_client/models/_account_asset_response.py +25 -0
- algokit_algod_client/models/_account_participation.py +53 -0
- algokit_algod_client/models/_account_state_delta.py +30 -0
- algokit_algod_client/models/_allocations_for_genesis_file.py +23 -0
- algokit_algod_client/models/_allocations_for_genesis_file_state_model.py +42 -0
- algokit_algod_client/models/_application.py +23 -0
- algokit_algod_client/models/_application_initial_states.py +37 -0
- algokit_algod_client/models/_application_kvstorage.py +29 -0
- algokit_algod_client/models/_application_local_state.py +33 -0
- algokit_algod_client/models/_application_params.py +63 -0
- algokit_algod_client/models/_application_state_operation.py +41 -0
- algokit_algod_client/models/_application_state_schema.py +22 -0
- algokit_algod_client/models/_asset.py +23 -0
- algokit_algod_client/models/_asset_holding.py +29 -0
- algokit_algod_client/models/_asset_params.py +102 -0
- algokit_algod_client/models/_avm_key_value.py +28 -0
- algokit_algod_client/models/_avm_value.py +32 -0
- algokit_algod_client/models/_block.py +363 -0
- algokit_algod_client/models/_block_hash_response.py +14 -0
- algokit_algod_client/models/_block_txids_response.py +14 -0
- algokit_algod_client/models/_box.py +36 -0
- algokit_algod_client/models/_box_descriptor.py +24 -0
- algokit_algod_client/models/_boxes_response.py +21 -0
- algokit_algod_client/models/_build_version_contains_the_current_algod_build_version_information.py +34 -0
- algokit_algod_client/models/_compile_response.py +24 -0
- algokit_algod_client/models/_disassemble_response.py +14 -0
- algokit_algod_client/models/_error_response.py +22 -0
- algokit_algod_client/models/_eval_delta.py +32 -0
- algokit_algod_client/models/_eval_delta_key_value.py +28 -0
- algokit_algod_client/models/_genesis_file_in_json.py +53 -0
- algokit_algod_client/models/_get_block_time_stamp_offset_response.py +14 -0
- algokit_algod_client/models/_get_sync_round_response.py +14 -0
- algokit_algod_client/models/_ledger_state_delta.py +389 -0
- algokit_algod_client/models/_light_block_header_proof.py +32 -0
- algokit_algod_client/models/_node_status_response.py +118 -0
- algokit_algod_client/models/_pending_transaction_response.py +91 -0
- algokit_algod_client/models/_pending_transactions_response.py +29 -0
- algokit_algod_client/models/_post_transactions_response.py +14 -0
- algokit_algod_client/models/_scratch_change.py +23 -0
- algokit_algod_client/models/_serde_helpers.py +241 -0
- algokit_algod_client/models/_simulate_initial_states.py +25 -0
- algokit_algod_client/models/_simulate_request.py +54 -0
- algokit_algod_client/models/_simulate_request_transaction_group.py +25 -0
- algokit_algod_client/models/_simulate_response.py +44 -0
- algokit_algod_client/models/_simulate_trace_config.py +30 -0
- algokit_algod_client/models/_simulate_transaction_group_result.py +46 -0
- algokit_algod_client/models/_simulate_transaction_result.py +41 -0
- algokit_algod_client/models/_simulate_unnamed_resources_accessed.py +64 -0
- algokit_algod_client/models/_simulation_eval_overrides.py +40 -0
- algokit_algod_client/models/_simulation_opcode_trace_unit.py +55 -0
- algokit_algod_client/models/_simulation_transaction_exec_trace.py +82 -0
- algokit_algod_client/models/_source_map.py +30 -0
- algokit_algod_client/models/_state_delta.py +6 -0
- algokit_algod_client/models/_state_proof.py +28 -0
- algokit_algod_client/models/_state_proof_message.py +44 -0
- algokit_algod_client/models/_supply_response.py +26 -0
- algokit_algod_client/models/_teal_key_value.py +28 -0
- algokit_algod_client/models/_teal_key_value_store.py +6 -0
- algokit_algod_client/models/_teal_value.py +32 -0
- algokit_algod_client/models/_transaction_group_ledger_state_deltas_for_round_response.py +21 -0
- algokit_algod_client/models/_transaction_parameters_response.py +45 -0
- algokit_algod_client/models/_transaction_proof.py +44 -0
- algokit_algod_client/models/_version_contains_the_current_algod_version.py +38 -0
- algokit_algod_client/models/suggested_params.py +42 -0
- algokit_algod_client/py.typed +1 -0
- algokit_algod_client/types.py +7 -0
- algokit_algosdk/__init__.py +38 -0
- algokit_algosdk/account.py +32 -0
- algokit_algosdk/app_access.py +228 -0
- algokit_algosdk/box_reference.py +100 -0
- algokit_algosdk/constants.py +147 -0
- algokit_algosdk/encoding.py +89 -0
- algokit_algosdk/error.py +180 -0
- algokit_algosdk/logic.py +61 -0
- algokit_algosdk/logicsig.py +218 -0
- algokit_algosdk/mnemonic.py +216 -0
- algokit_algosdk/multisig.py +161 -0
- algokit_algosdk/py.typed +0 -0
- algokit_algosdk/transaction.py +596 -0
- algokit_algosdk/wordlist.py +2054 -0
- algokit_common/__init__.py +50 -0
- algokit_common/address.py +34 -0
- algokit_common/constants.py +47 -0
- algokit_common/hashing.py +25 -0
- algokit_common/py.typed +0 -0
- algokit_common/serde/__init__.py +40 -0
- algokit_common/serde/_core.py +610 -0
- algokit_common/serde/_primitives.py +135 -0
- algokit_common/source_map.py +158 -0
- algokit_indexer_client/__init__.py +10 -0
- algokit_indexer_client/client.py +1456 -0
- algokit_indexer_client/config.py +36 -0
- algokit_indexer_client/exceptions.py +59 -0
- algokit_indexer_client/models/__init__.py +148 -0
- algokit_indexer_client/models/_account.py +161 -0
- algokit_indexer_client/models/_account_participation.py +53 -0
- algokit_indexer_client/models/_account_response.py +19 -0
- algokit_indexer_client/models/_account_state_delta.py +29 -0
- algokit_indexer_client/models/_accounts_response.py +29 -0
- algokit_indexer_client/models/_application.py +35 -0
- algokit_indexer_client/models/_application_local_state.py +45 -0
- algokit_indexer_client/models/_application_local_states_response.py +29 -0
- algokit_indexer_client/models/_application_log_data.py +28 -0
- algokit_indexer_client/models/_application_logs_response.py +33 -0
- algokit_indexer_client/models/_application_params.py +62 -0
- algokit_indexer_client/models/_application_response.py +20 -0
- algokit_indexer_client/models/_application_state_schema.py +22 -0
- algokit_indexer_client/models/_applications_response.py +29 -0
- algokit_indexer_client/models/_asset.py +35 -0
- algokit_indexer_client/models/_asset_balances_response.py +29 -0
- algokit_indexer_client/models/_asset_holding.py +41 -0
- algokit_indexer_client/models/_asset_holdings_response.py +29 -0
- algokit_indexer_client/models/_asset_params.py +102 -0
- algokit_indexer_client/models/_asset_response.py +19 -0
- algokit_indexer_client/models/_assets_response.py +29 -0
- algokit_indexer_client/models/_block.py +150 -0
- algokit_indexer_client/models/_block_headers_response.py +29 -0
- algokit_indexer_client/models/_block_rewards.py +38 -0
- algokit_indexer_client/models/_block_upgrade_state.py +34 -0
- algokit_indexer_client/models/_block_upgrade_vote.py +26 -0
- algokit_indexer_client/models/_box.py +36 -0
- algokit_indexer_client/models/_box_descriptor.py +24 -0
- algokit_indexer_client/models/_box_reference.py +28 -0
- algokit_indexer_client/models/_boxes_response.py +29 -0
- algokit_indexer_client/models/_error_response.py +18 -0
- algokit_indexer_client/models/_eval_delta.py +32 -0
- algokit_indexer_client/models/_eval_delta_key_value.py +28 -0
- algokit_indexer_client/models/_hash_factory.py +14 -0
- algokit_indexer_client/models/_hb_proof_fields.py +57 -0
- algokit_indexer_client/models/_health_check.py +42 -0
- algokit_indexer_client/models/_holding_ref.py +23 -0
- algokit_indexer_client/models/_indexer_state_proof_message.py +40 -0
- algokit_indexer_client/models/_locals_ref.py +23 -0
- algokit_indexer_client/models/_merkle_array_proof.py +29 -0
- algokit_indexer_client/models/_mini_asset_holding.py +38 -0
- algokit_indexer_client/models/_on_completion.py +25 -0
- algokit_indexer_client/models/_participation_updates.py +22 -0
- algokit_indexer_client/models/_resource_ref.py +42 -0
- algokit_indexer_client/models/_serde_helpers.py +241 -0
- algokit_indexer_client/models/_state_delta.py +6 -0
- algokit_indexer_client/models/_state_proof_fields.py +57 -0
- algokit_indexer_client/models/_state_proof_participant.py +20 -0
- algokit_indexer_client/models/_state_proof_reveal.py +25 -0
- algokit_indexer_client/models/_state_proof_sig_slot.py +20 -0
- algokit_indexer_client/models/_state_proof_signature.py +37 -0
- algokit_indexer_client/models/_state_proof_tracking.py +32 -0
- algokit_indexer_client/models/_state_proof_verifier.py +24 -0
- algokit_indexer_client/models/_state_schema.py +25 -0
- algokit_indexer_client/models/_teal_key_value.py +28 -0
- algokit_indexer_client/models/_teal_key_value_store.py +6 -0
- algokit_indexer_client/models/_teal_value.py +32 -0
- algokit_indexer_client/models/_transaction.py +213 -0
- algokit_indexer_client/models/_transaction_application.py +105 -0
- algokit_indexer_client/models/_transaction_asset_config.py +31 -0
- algokit_indexer_client/models/_transaction_asset_freeze.py +29 -0
- algokit_indexer_client/models/_transaction_asset_transfer.py +41 -0
- algokit_indexer_client/models/_transaction_heartbeat.py +52 -0
- algokit_indexer_client/models/_transaction_keyreg.py +59 -0
- algokit_indexer_client/models/_transaction_payment.py +33 -0
- algokit_indexer_client/models/_transaction_response.py +19 -0
- algokit_indexer_client/models/_transaction_signature.py +35 -0
- algokit_indexer_client/models/_transaction_signature_logicsig.py +59 -0
- algokit_indexer_client/models/_transaction_signature_multisig.py +36 -0
- algokit_indexer_client/models/_transaction_signature_multisig_subsignature.py +28 -0
- algokit_indexer_client/models/_transaction_state_proof.py +32 -0
- algokit_indexer_client/models/_transactions_response.py +29 -0
- algokit_indexer_client/py.typed +1 -0
- algokit_indexer_client/types.py +7 -0
- algokit_kmd_client/__init__.py +10 -0
- algokit_kmd_client/client.py +1240 -0
- algokit_kmd_client/config.py +36 -0
- algokit_kmd_client/exceptions.py +59 -0
- algokit_kmd_client/models/__init__.py +112 -0
- algokit_kmd_client/models/_classical_signatures.py +4 -0
- algokit_kmd_client/models/_create_wallet_request.py +30 -0
- algokit_kmd_client/models/_create_wallet_response.py +19 -0
- algokit_kmd_client/models/_delete_key_request.py +27 -0
- algokit_kmd_client/models/_delete_multisig_request.py +27 -0
- algokit_kmd_client/models/_digest_represents_a32_byte_value_holding_the256_bit_hash_digest.py +4 -0
- algokit_kmd_client/models/_ed25519_public_key.py +4 -0
- algokit_kmd_client/models/_export_key_request.py +27 -0
- algokit_kmd_client/models/_export_key_response.py +24 -0
- algokit_kmd_client/models/_export_master_key_request.py +22 -0
- algokit_kmd_client/models/_export_master_key_response.py +18 -0
- algokit_kmd_client/models/_export_multisig_request.py +23 -0
- algokit_kmd_client/models/_export_multisig_response.py +26 -0
- algokit_kmd_client/models/_generate_key_request.py +18 -0
- algokit_kmd_client/models/_generate_key_response.py +19 -0
- algokit_kmd_client/models/_import_key_request.py +28 -0
- algokit_kmd_client/models/_import_key_response.py +19 -0
- algokit_kmd_client/models/_import_multisig_request.py +30 -0
- algokit_kmd_client/models/_import_multisig_response.py +19 -0
- algokit_kmd_client/models/_init_wallet_handle_token_request.py +22 -0
- algokit_kmd_client/models/_init_wallet_handle_token_response.py +18 -0
- algokit_kmd_client/models/_list_keys_request.py +18 -0
- algokit_kmd_client/models/_list_keys_response.py +18 -0
- algokit_kmd_client/models/_list_multisig_request.py +18 -0
- algokit_kmd_client/models/_list_multisig_response.py +18 -0
- algokit_kmd_client/models/_list_wallets_request.py +11 -0
- algokit_kmd_client/models/_list_wallets_response.py +25 -0
- algokit_kmd_client/models/_master_derivation_key.py +4 -0
- algokit_kmd_client/models/_multisig_sig.py +33 -0
- algokit_kmd_client/models/_multisig_subsig.py +23 -0
- algokit_kmd_client/models/_public_key.py +4 -0
- algokit_kmd_client/models/_release_wallet_handle_token_request.py +18 -0
- algokit_kmd_client/models/_rename_wallet_request.py +26 -0
- algokit_kmd_client/models/_rename_wallet_response.py +19 -0
- algokit_kmd_client/models/_renew_wallet_handle_token_request.py +18 -0
- algokit_kmd_client/models/_renew_wallet_handle_token_response.py +19 -0
- algokit_kmd_client/models/_serde_helpers.py +241 -0
- algokit_kmd_client/models/_sign_multisig_response.py +24 -0
- algokit_kmd_client/models/_sign_multisig_txn_request.py +45 -0
- algokit_kmd_client/models/_sign_program_multisig_request.py +50 -0
- algokit_kmd_client/models/_sign_program_multisig_response.py +24 -0
- algokit_kmd_client/models/_sign_program_request.py +37 -0
- algokit_kmd_client/models/_sign_program_response.py +24 -0
- algokit_kmd_client/models/_sign_transaction_response.py +24 -0
- algokit_kmd_client/models/_sign_txn_request.py +36 -0
- algokit_kmd_client/models/_signature.py +4 -0
- algokit_kmd_client/models/_tx_type.py +4 -0
- algokit_kmd_client/models/_versions_request.py +11 -0
- algokit_kmd_client/models/_versions_response.py +19 -0
- algokit_kmd_client/models/_wallet.py +38 -0
- algokit_kmd_client/models/_wallet_handle.py +24 -0
- algokit_kmd_client/models/_wallet_info_request.py +18 -0
- algokit_kmd_client/models/_wallet_info_response.py +19 -0
- algokit_kmd_client/py.typed +1 -0
- algokit_kmd_client/types.py +7 -0
- algokit_transact/__init__.py +190 -0
- algokit_transact/codec/__init__.py +0 -0
- algokit_transact/codec/msgpack.py +11 -0
- algokit_transact/codec/serde.py +7 -0
- algokit_transact/codec/signed.py +57 -0
- algokit_transact/codec/transaction.py +65 -0
- algokit_transact/exceptions.py +17 -0
- algokit_transact/logicsig.py +220 -0
- algokit_transact/models/__init__.py +0 -0
- algokit_transact/models/app_call.py +447 -0
- algokit_transact/models/asset_config.py +19 -0
- algokit_transact/models/asset_freeze.py +11 -0
- algokit_transact/models/asset_transfer.py +13 -0
- algokit_transact/models/common.py +17 -0
- algokit_transact/models/heartbeat.py +21 -0
- algokit_transact/models/key_registration.py +14 -0
- algokit_transact/models/payment.py +14 -0
- algokit_transact/models/signed_transaction.py +21 -0
- algokit_transact/models/state_proof.py +150 -0
- algokit_transact/models/transaction.py +88 -0
- algokit_transact/multisig.py +93 -0
- algokit_transact/ops/__init__.py +0 -0
- algokit_transact/ops/fees.py +47 -0
- algokit_transact/ops/group.py +28 -0
- algokit_transact/ops/ids.py +14 -0
- algokit_transact/ops/validate.py +503 -0
- algokit_transact/py.typed +0 -0
- algokit_transact/signer.py +195 -0
- algokit_transact/signing/__init__.py +0 -0
- algokit_transact/signing/logic_signature.py +19 -0
- algokit_transact/signing/multisig.py +84 -0
- algokit_transact/signing/types.py +39 -0
- algokit_transact/signing/validation.py +63 -0
- algokit_utils/__init__.py +23 -0
- algokit_utils/_debugging.py +304 -0
- algokit_utils/accounts/__init__.py +2 -0
- algokit_utils/accounts/account_manager.py +1051 -0
- algokit_utils/accounts/kmd_account_manager.py +206 -0
- algokit_utils/algo25.py +46 -0
- algokit_utils/algorand.py +383 -0
- algokit_utils/applications/__init__.py +7 -0
- algokit_utils/applications/abi.py +280 -0
- algokit_utils/applications/app_client.py +2193 -0
- algokit_utils/applications/app_deployer.py +788 -0
- algokit_utils/applications/app_factory.py +1140 -0
- algokit_utils/applications/app_manager.py +575 -0
- algokit_utils/applications/app_spec/__init__.py +6 -0
- algokit_utils/applications/enums.py +40 -0
- algokit_utils/assets/__init__.py +1 -0
- algokit_utils/assets/asset_manager.py +344 -0
- algokit_utils/clients/__init__.py +41 -0
- algokit_utils/clients/client_manager.py +756 -0
- algokit_utils/clients/dispenser_api_client.py +212 -0
- algokit_utils/common.py +40 -0
- algokit_utils/config.py +159 -0
- algokit_utils/errors/__init__.py +1 -0
- algokit_utils/errors/logic_error.py +160 -0
- algokit_utils/models/__init__.py +7 -0
- algokit_utils/models/account.py +12 -0
- algokit_utils/models/amount.py +198 -0
- algokit_utils/models/application.py +90 -0
- algokit_utils/models/network.py +29 -0
- algokit_utils/models/simulate.py +7 -0
- algokit_utils/models/state.py +53 -0
- algokit_utils/models/transaction.py +49 -0
- algokit_utils/protocols/__init__.py +3 -0
- algokit_utils/protocols/account.py +11 -0
- algokit_utils/protocols/signer.py +17 -0
- algokit_utils/protocols/typed_clients.py +110 -0
- algokit_utils/py.typed +0 -0
- algokit_utils/transact.py +195 -0
- algokit_utils/transactions/__init__.py +3 -0
- algokit_utils/transactions/builders/__init__.py +67 -0
- algokit_utils/transactions/builders/app.py +248 -0
- algokit_utils/transactions/builders/asset.py +256 -0
- algokit_utils/transactions/builders/common.py +263 -0
- algokit_utils/transactions/builders/keyreg.py +103 -0
- algokit_utils/transactions/builders/method_call.py +380 -0
- algokit_utils/transactions/builders/payment.py +43 -0
- algokit_utils/transactions/composer_resources.py +409 -0
- algokit_utils/transactions/fee_coverage.py +79 -0
- algokit_utils/transactions/helpers.py +9 -0
- algokit_utils/transactions/transaction_composer.py +1574 -0
- algokit_utils/transactions/transaction_creator.py +699 -0
- algokit_utils/transactions/transaction_sender.py +1240 -0
- algokit_utils/transactions/types.py +262 -0
- algokit_utils-5.0.0a3.dist-info/METADATA +105 -0
- algokit_utils-5.0.0a3.dist-info/RECORD +337 -0
- algokit_utils-5.0.0a3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
from dataclasses import dataclass, replace
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from algokit_algod_client import models as algod_models
|
|
6
|
+
from algokit_common import get_application_address
|
|
7
|
+
from algokit_common.constants import MAX_ACCOUNT_REFERENCES, MAX_OVERALL_REFERENCES
|
|
8
|
+
from algokit_transact.models.app_call import AppCallTransactionFields, BoxReference
|
|
9
|
+
from algokit_transact.models.transaction import Transaction, TransactionType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def populate_transaction_resources( # noqa: C901, PLR0912
|
|
13
|
+
transaction: Transaction,
|
|
14
|
+
resources_accessed: algod_models.SimulateUnnamedResourcesAccessed,
|
|
15
|
+
group_index: int,
|
|
16
|
+
) -> Transaction:
|
|
17
|
+
"""
|
|
18
|
+
Populate transaction-level resources for app call transactions
|
|
19
|
+
"""
|
|
20
|
+
if transaction.transaction_type != TransactionType.AppCall or transaction.application_call is None:
|
|
21
|
+
return transaction
|
|
22
|
+
|
|
23
|
+
# Check for unexpected resources at transaction level
|
|
24
|
+
if resources_accessed.boxes or resources_accessed.extra_box_refs:
|
|
25
|
+
raise ValueError("Unexpected boxes at the transaction level")
|
|
26
|
+
if resources_accessed.app_locals:
|
|
27
|
+
raise ValueError("Unexpected app locals at the transaction level")
|
|
28
|
+
if resources_accessed.asset_holdings:
|
|
29
|
+
raise ValueError("Unexpected asset holdings at the transaction level")
|
|
30
|
+
|
|
31
|
+
app_call = transaction.application_call
|
|
32
|
+
updated_app_call = app_call
|
|
33
|
+
accounts_count = len(app_call.account_references or [])
|
|
34
|
+
apps_count = len(app_call.app_references or [])
|
|
35
|
+
assets_count = len(app_call.asset_references or [])
|
|
36
|
+
boxes_count = len(app_call.box_references or [])
|
|
37
|
+
|
|
38
|
+
# Populate accounts
|
|
39
|
+
if resources_accessed.accounts:
|
|
40
|
+
current_accounts = list(app_call.account_references or [])
|
|
41
|
+
for account in resources_accessed.accounts:
|
|
42
|
+
normalized = _normalize_address(account)
|
|
43
|
+
if normalized not in current_accounts:
|
|
44
|
+
current_accounts.append(normalized)
|
|
45
|
+
if len(current_accounts) != accounts_count:
|
|
46
|
+
updated_app_call = replace(updated_app_call, account_references=current_accounts)
|
|
47
|
+
accounts_count = len(current_accounts)
|
|
48
|
+
|
|
49
|
+
# Populate apps
|
|
50
|
+
if resources_accessed.apps:
|
|
51
|
+
current_apps = list(updated_app_call.app_references or [])
|
|
52
|
+
for app_id in resources_accessed.apps:
|
|
53
|
+
if app_id not in current_apps:
|
|
54
|
+
current_apps.append(app_id)
|
|
55
|
+
if len(current_apps) != apps_count:
|
|
56
|
+
updated_app_call = replace(updated_app_call, app_references=current_apps)
|
|
57
|
+
apps_count = len(current_apps)
|
|
58
|
+
|
|
59
|
+
# Populate assets
|
|
60
|
+
if resources_accessed.assets:
|
|
61
|
+
current_assets = list(updated_app_call.asset_references or [])
|
|
62
|
+
for asset_id in resources_accessed.assets:
|
|
63
|
+
if asset_id not in current_assets:
|
|
64
|
+
current_assets.append(asset_id)
|
|
65
|
+
if len(current_assets) != assets_count:
|
|
66
|
+
updated_app_call = replace(updated_app_call, asset_references=current_assets)
|
|
67
|
+
assets_count = len(current_assets)
|
|
68
|
+
|
|
69
|
+
# Validate reference limits
|
|
70
|
+
if accounts_count + assets_count + apps_count + boxes_count > MAX_OVERALL_REFERENCES:
|
|
71
|
+
raise ValueError(f"Resource reference limit of {MAX_OVERALL_REFERENCES} exceeded in transaction {group_index}")
|
|
72
|
+
|
|
73
|
+
if updated_app_call is app_call:
|
|
74
|
+
return transaction
|
|
75
|
+
return replace(transaction, application_call=updated_app_call)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GroupResourceType(Enum):
|
|
79
|
+
"""Describes different group resources"""
|
|
80
|
+
|
|
81
|
+
Account = "Account"
|
|
82
|
+
App = "App"
|
|
83
|
+
Asset = "Asset"
|
|
84
|
+
Box = "Box"
|
|
85
|
+
ExtraBoxRef = "ExtraBoxRef"
|
|
86
|
+
AssetHolding = "AssetHolding"
|
|
87
|
+
AppLocal = "AppLocal"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(slots=True)
|
|
91
|
+
class GroupResourceToPopulate:
|
|
92
|
+
type: GroupResourceType
|
|
93
|
+
data: Any
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def populate_group_resources( # noqa: C901, PLR0912
|
|
97
|
+
transactions: list[Transaction],
|
|
98
|
+
group_resources: algod_models.SimulateUnnamedResourcesAccessed,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Populate group-level resources for app call transactions
|
|
102
|
+
"""
|
|
103
|
+
remaining_accounts = list(group_resources.accounts or [])
|
|
104
|
+
remaining_apps = list(group_resources.apps or [])
|
|
105
|
+
remaining_assets = list(group_resources.assets or [])
|
|
106
|
+
remaining_boxes = list(group_resources.boxes or [])
|
|
107
|
+
|
|
108
|
+
# Process cross-reference resources first (app locals and asset holdings) as they are most restrictive
|
|
109
|
+
if group_resources.app_locals:
|
|
110
|
+
for app_local in group_resources.app_locals:
|
|
111
|
+
_populate_group_resource(transactions, GroupResourceToPopulate(GroupResourceType.AppLocal, app_local))
|
|
112
|
+
# Remove resources from remaining if we're adding them here
|
|
113
|
+
if app_local.address in remaining_accounts:
|
|
114
|
+
remaining_accounts.remove(app_local.address)
|
|
115
|
+
if app_local.app_id in remaining_apps:
|
|
116
|
+
remaining_apps.remove(app_local.app_id)
|
|
117
|
+
|
|
118
|
+
if group_resources.asset_holdings:
|
|
119
|
+
for asset_holding in group_resources.asset_holdings:
|
|
120
|
+
_populate_group_resource(
|
|
121
|
+
transactions, GroupResourceToPopulate(GroupResourceType.AssetHolding, asset_holding)
|
|
122
|
+
)
|
|
123
|
+
# Remove resources from remaining if we're adding them here
|
|
124
|
+
if asset_holding.address in remaining_accounts:
|
|
125
|
+
remaining_accounts.remove(asset_holding.address)
|
|
126
|
+
if asset_holding.asset_id in remaining_assets:
|
|
127
|
+
remaining_assets.remove(asset_holding.asset_id)
|
|
128
|
+
|
|
129
|
+
# Process accounts next
|
|
130
|
+
for account in remaining_accounts:
|
|
131
|
+
_populate_group_resource(transactions, GroupResourceToPopulate(GroupResourceType.Account, account))
|
|
132
|
+
|
|
133
|
+
# Process boxes
|
|
134
|
+
for box_ref in remaining_boxes:
|
|
135
|
+
_populate_group_resource(
|
|
136
|
+
transactions,
|
|
137
|
+
GroupResourceToPopulate(
|
|
138
|
+
GroupResourceType.Box,
|
|
139
|
+
BoxReference(app_id=box_ref.app_id, name=box_ref.name),
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
# Remove apps as resource if we're adding it here
|
|
143
|
+
if box_ref.app_id in remaining_apps:
|
|
144
|
+
remaining_apps.remove(box_ref.app_id)
|
|
145
|
+
|
|
146
|
+
# Process assets
|
|
147
|
+
for asset in remaining_assets:
|
|
148
|
+
_populate_group_resource(transactions, GroupResourceToPopulate(GroupResourceType.Asset, asset))
|
|
149
|
+
|
|
150
|
+
# Process remaining apps
|
|
151
|
+
for app in remaining_apps:
|
|
152
|
+
_populate_group_resource(transactions, GroupResourceToPopulate(GroupResourceType.App, app))
|
|
153
|
+
|
|
154
|
+
# Handle extra box refs
|
|
155
|
+
if group_resources.extra_box_refs:
|
|
156
|
+
for _ in range(group_resources.extra_box_refs):
|
|
157
|
+
_populate_group_resource(transactions, GroupResourceToPopulate(GroupResourceType.ExtraBoxRef, None))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _is_app_call_below_resource_limit(txn: Transaction) -> bool:
|
|
161
|
+
if txn.transaction_type != TransactionType.AppCall or txn.application_call is None:
|
|
162
|
+
return False
|
|
163
|
+
if txn.application_call.access_references:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
accounts_count = len(txn.application_call.account_references or [])
|
|
167
|
+
assets_count = len(txn.application_call.asset_references or [])
|
|
168
|
+
apps_count = len(txn.application_call.app_references or [])
|
|
169
|
+
boxes_count = len(txn.application_call.box_references or [])
|
|
170
|
+
|
|
171
|
+
return accounts_count + assets_count + apps_count + boxes_count < MAX_OVERALL_REFERENCES
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _get_app_address(app_id: int) -> str:
|
|
175
|
+
return get_application_address(app_id)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _populate_group_resource( # noqa: C901, PLR0912, PLR0915
|
|
179
|
+
transactions: list[Transaction],
|
|
180
|
+
resource: GroupResourceToPopulate,
|
|
181
|
+
) -> None:
|
|
182
|
+
# For asset holdings and app locals, first try to find a transaction that already has the account available
|
|
183
|
+
if resource.type in (GroupResourceType.AssetHolding, GroupResourceType.AppLocal):
|
|
184
|
+
account = _normalize_address(resource.data.address)
|
|
185
|
+
|
|
186
|
+
# Try to find a transaction that already has the account available
|
|
187
|
+
group_index1 = -1
|
|
188
|
+
for i, txn in enumerate(transactions):
|
|
189
|
+
if not _is_app_call_below_resource_limit(txn):
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
app_call = txn.application_call
|
|
193
|
+
assert app_call is not None
|
|
194
|
+
|
|
195
|
+
# Check if account is in foreign accounts array
|
|
196
|
+
if app_call.account_references and account in app_call.account_references:
|
|
197
|
+
group_index1 = i
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
# Check if account is available as an app account
|
|
201
|
+
if app_call.app_references:
|
|
202
|
+
found = False
|
|
203
|
+
for app_id in app_call.app_references:
|
|
204
|
+
if account == _get_app_address(app_id):
|
|
205
|
+
found = True
|
|
206
|
+
break
|
|
207
|
+
if found:
|
|
208
|
+
group_index1 = i
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
# Check if account appears in any app call transaction fields
|
|
212
|
+
if txn.sender == account:
|
|
213
|
+
group_index1 = i
|
|
214
|
+
break
|
|
215
|
+
|
|
216
|
+
if group_index1 != -1:
|
|
217
|
+
app_call = transactions[group_index1].application_call
|
|
218
|
+
assert app_call is not None
|
|
219
|
+
if resource.type == GroupResourceType.AssetHolding:
|
|
220
|
+
current_assets = list(app_call.asset_references or [])
|
|
221
|
+
if resource.data.asset_id not in current_assets:
|
|
222
|
+
current_assets.append(resource.data.asset_id)
|
|
223
|
+
app_call = replace(app_call, asset_references=current_assets)
|
|
224
|
+
_set_app_call(transactions, group_index1, app_call)
|
|
225
|
+
else:
|
|
226
|
+
current_apps = list(app_call.app_references or [])
|
|
227
|
+
if resource.data.app_id not in current_apps:
|
|
228
|
+
current_apps.append(resource.data.app_id)
|
|
229
|
+
app_call = replace(app_call, app_references=current_apps)
|
|
230
|
+
_set_app_call(transactions, group_index1, app_call)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Try to find a transaction that has the asset/app available and space for account
|
|
234
|
+
group_index2 = -1
|
|
235
|
+
for i, txn in enumerate(transactions):
|
|
236
|
+
if not _is_app_call_below_resource_limit(txn):
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
app_call = txn.application_call
|
|
240
|
+
assert app_call is not None
|
|
241
|
+
if len(app_call.account_references or []) >= MAX_ACCOUNT_REFERENCES:
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
if resource.type == GroupResourceType.AssetHolding:
|
|
245
|
+
if app_call.asset_references and resource.data.asset_id in app_call.asset_references:
|
|
246
|
+
group_index2 = i
|
|
247
|
+
break
|
|
248
|
+
elif (
|
|
249
|
+
app_call.app_references and resource.data.app_id in app_call.app_references
|
|
250
|
+
) or app_call.app_id == resource.data.app_id:
|
|
251
|
+
group_index2 = i
|
|
252
|
+
break
|
|
253
|
+
|
|
254
|
+
if group_index2 != -1:
|
|
255
|
+
app_call = transactions[group_index2].application_call
|
|
256
|
+
assert app_call is not None
|
|
257
|
+
current_accounts = list(app_call.account_references or [])
|
|
258
|
+
if account not in current_accounts:
|
|
259
|
+
current_accounts.append(account)
|
|
260
|
+
app_call = replace(app_call, account_references=current_accounts)
|
|
261
|
+
_set_app_call(transactions, group_index2, app_call)
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
# For boxes, first try to find a transaction that already has the app available
|
|
265
|
+
if resource.type == GroupResourceType.Box:
|
|
266
|
+
group_index = -1
|
|
267
|
+
for i, txn in enumerate(transactions):
|
|
268
|
+
if not _is_app_call_below_resource_limit(txn):
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
app_call = txn.application_call
|
|
272
|
+
assert app_call is not None
|
|
273
|
+
if (
|
|
274
|
+
app_call.app_references and resource.data.app_id in app_call.app_references
|
|
275
|
+
) or app_call.app_id == resource.data.app_id:
|
|
276
|
+
group_index = i
|
|
277
|
+
break
|
|
278
|
+
|
|
279
|
+
if group_index != -1:
|
|
280
|
+
app_call = transactions[group_index].application_call
|
|
281
|
+
assert app_call is not None
|
|
282
|
+
current_boxes = list(app_call.box_references or [])
|
|
283
|
+
exists = any(b.app_id == resource.data.app_id and b.name == resource.data.name for b in current_boxes)
|
|
284
|
+
if not exists:
|
|
285
|
+
current_boxes.append(BoxReference(app_id=resource.data.app_id, name=resource.data.name))
|
|
286
|
+
app_call = replace(app_call, box_references=current_boxes)
|
|
287
|
+
_set_app_call(transactions, group_index, app_call)
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
# Find the first transaction that can accommodate the resource
|
|
291
|
+
group_index = -1
|
|
292
|
+
for i, txn in enumerate(transactions):
|
|
293
|
+
if txn.transaction_type != TransactionType.AppCall or txn.application_call is None:
|
|
294
|
+
continue
|
|
295
|
+
if txn.application_call.access_references:
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
app_call = txn.application_call
|
|
299
|
+
accounts_count = len(app_call.account_references or [])
|
|
300
|
+
assets_count = len(app_call.asset_references or [])
|
|
301
|
+
apps_count = len(app_call.app_references or [])
|
|
302
|
+
boxes_count = len(app_call.box_references or [])
|
|
303
|
+
|
|
304
|
+
if resource.type == GroupResourceType.Account:
|
|
305
|
+
if accounts_count + assets_count + apps_count + boxes_count < MAX_OVERALL_REFERENCES:
|
|
306
|
+
group_index = i
|
|
307
|
+
break
|
|
308
|
+
elif resource.type in (GroupResourceType.AssetHolding, GroupResourceType.AppLocal):
|
|
309
|
+
if accounts_count + assets_count + apps_count + boxes_count < MAX_OVERALL_REFERENCES - 1:
|
|
310
|
+
group_index = i
|
|
311
|
+
break
|
|
312
|
+
elif resource.type == GroupResourceType.Box:
|
|
313
|
+
if resource.data.app_id != 0:
|
|
314
|
+
if accounts_count + assets_count + apps_count + boxes_count < MAX_OVERALL_REFERENCES - 1:
|
|
315
|
+
group_index = i
|
|
316
|
+
break
|
|
317
|
+
elif accounts_count + assets_count + apps_count + boxes_count < MAX_OVERALL_REFERENCES:
|
|
318
|
+
group_index = i
|
|
319
|
+
break
|
|
320
|
+
elif accounts_count + assets_count + apps_count + boxes_count < MAX_OVERALL_REFERENCES:
|
|
321
|
+
group_index = i
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
if group_index == -1:
|
|
325
|
+
raise ValueError("No more transactions below reference limit. Add another app call to the group.")
|
|
326
|
+
|
|
327
|
+
app_call = transactions[group_index].application_call
|
|
328
|
+
assert app_call is not None
|
|
329
|
+
|
|
330
|
+
if resource.type == GroupResourceType.Account:
|
|
331
|
+
current_accounts = list(app_call.account_references or [])
|
|
332
|
+
account = _normalize_address(resource.data)
|
|
333
|
+
if account not in current_accounts:
|
|
334
|
+
current_accounts.append(account)
|
|
335
|
+
app_call = replace(app_call, account_references=current_accounts)
|
|
336
|
+
_set_app_call(transactions, group_index, app_call)
|
|
337
|
+
|
|
338
|
+
elif resource.type == GroupResourceType.App:
|
|
339
|
+
current_apps = list(app_call.app_references or [])
|
|
340
|
+
if resource.data not in current_apps:
|
|
341
|
+
current_apps.append(resource.data)
|
|
342
|
+
app_call = replace(app_call, app_references=current_apps)
|
|
343
|
+
_set_app_call(transactions, group_index, app_call)
|
|
344
|
+
|
|
345
|
+
elif resource.type == GroupResourceType.Box:
|
|
346
|
+
current_boxes = list(app_call.box_references or [])
|
|
347
|
+
exists = any(b.app_id == resource.data.app_id and b.name == resource.data.name for b in current_boxes)
|
|
348
|
+
if not exists:
|
|
349
|
+
current_boxes.append(BoxReference(app_id=resource.data.app_id, name=resource.data.name))
|
|
350
|
+
app_call = replace(app_call, box_references=current_boxes)
|
|
351
|
+
_set_app_call(transactions, group_index, app_call)
|
|
352
|
+
|
|
353
|
+
if resource.data.app_id != 0:
|
|
354
|
+
current_apps = list(app_call.app_references or [])
|
|
355
|
+
if resource.data.app_id not in current_apps:
|
|
356
|
+
current_apps.append(resource.data.app_id)
|
|
357
|
+
app_call = replace(app_call, app_references=current_apps)
|
|
358
|
+
_set_app_call(transactions, group_index, app_call)
|
|
359
|
+
|
|
360
|
+
elif resource.type == GroupResourceType.ExtraBoxRef:
|
|
361
|
+
current_boxes = list(app_call.box_references or [])
|
|
362
|
+
current_boxes.append(BoxReference(app_id=0, name=b""))
|
|
363
|
+
app_call = replace(app_call, box_references=current_boxes)
|
|
364
|
+
_set_app_call(transactions, group_index, app_call)
|
|
365
|
+
|
|
366
|
+
elif resource.type == GroupResourceType.AssetHolding:
|
|
367
|
+
current_assets = list(app_call.asset_references or [])
|
|
368
|
+
if resource.data.asset_id not in current_assets:
|
|
369
|
+
current_assets.append(resource.data.asset_id)
|
|
370
|
+
app_call = replace(app_call, asset_references=current_assets)
|
|
371
|
+
_set_app_call(transactions, group_index, app_call)
|
|
372
|
+
|
|
373
|
+
current_accounts = list(app_call.account_references or [])
|
|
374
|
+
account = _normalize_address(resource.data.address)
|
|
375
|
+
if account not in current_accounts:
|
|
376
|
+
current_accounts.append(account)
|
|
377
|
+
app_call = replace(app_call, account_references=current_accounts)
|
|
378
|
+
_set_app_call(transactions, group_index, app_call)
|
|
379
|
+
|
|
380
|
+
elif resource.type == GroupResourceType.AppLocal:
|
|
381
|
+
current_apps = list(app_call.app_references or [])
|
|
382
|
+
if resource.data.app_id not in current_apps:
|
|
383
|
+
current_apps.append(resource.data.app_id)
|
|
384
|
+
app_call = replace(app_call, app_references=current_apps)
|
|
385
|
+
_set_app_call(transactions, group_index, app_call)
|
|
386
|
+
|
|
387
|
+
current_accounts = list(app_call.account_references or [])
|
|
388
|
+
account = _normalize_address(resource.data.address)
|
|
389
|
+
if account not in current_accounts:
|
|
390
|
+
current_accounts.append(account)
|
|
391
|
+
app_call = replace(app_call, account_references=current_accounts)
|
|
392
|
+
_set_app_call(transactions, group_index, app_call)
|
|
393
|
+
|
|
394
|
+
elif resource.type == GroupResourceType.Asset:
|
|
395
|
+
current_assets = list(app_call.asset_references or [])
|
|
396
|
+
if resource.data not in current_assets:
|
|
397
|
+
current_assets.append(resource.data)
|
|
398
|
+
app_call = replace(app_call, asset_references=current_assets)
|
|
399
|
+
_set_app_call(transactions, group_index, app_call)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _set_app_call(transactions: list[Transaction], index: int, app_call: AppCallTransactionFields) -> None:
|
|
403
|
+
transactions[index] = replace(transactions[index], application_call=app_call)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _normalize_address(value: str | bytes) -> str:
|
|
407
|
+
if isinstance(value, bytes):
|
|
408
|
+
return value.decode("utf-8")
|
|
409
|
+
return value
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum, auto
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FeeDeltaType(Enum):
|
|
8
|
+
"""Describes the type of fee delta"""
|
|
9
|
+
|
|
10
|
+
DEFICIT = auto()
|
|
11
|
+
SURPLUS = auto()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True, frozen=True)
|
|
15
|
+
class FeeDelta:
|
|
16
|
+
"""Represents a difference between required and provided fee amounts."""
|
|
17
|
+
|
|
18
|
+
type: FeeDeltaType
|
|
19
|
+
data: int
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_int(value: int) -> "FeeDelta | None":
|
|
23
|
+
if value > 0:
|
|
24
|
+
return FeeDelta(FeeDeltaType.DEFICIT, value)
|
|
25
|
+
if value < 0:
|
|
26
|
+
return FeeDelta(FeeDeltaType.SURPLUS, -value)
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def add(lhs: "FeeDelta | None", rhs: "FeeDelta | None") -> "FeeDelta | None":
|
|
31
|
+
if lhs is None:
|
|
32
|
+
return rhs
|
|
33
|
+
if rhs is None:
|
|
34
|
+
return lhs
|
|
35
|
+
return FeeDelta.from_int(FeeDelta.to_int(lhs) + FeeDelta.to_int(rhs))
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def to_int(delta: "FeeDelta") -> int:
|
|
39
|
+
return delta.data if delta.type is FeeDeltaType.DEFICIT else -delta.data
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def amount(delta: "FeeDelta") -> int:
|
|
43
|
+
return delta.data
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def is_deficit(delta: "FeeDelta") -> bool:
|
|
47
|
+
return delta.type is FeeDeltaType.DEFICIT
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def is_surplus(delta: "FeeDelta") -> bool:
|
|
51
|
+
return delta.type is FeeDeltaType.SURPLUS
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(slots=True, frozen=True, order=True)
|
|
55
|
+
class FeePriority:
|
|
56
|
+
"""Priority wrapper used when deciding which transactions need additional fees applied first."""
|
|
57
|
+
|
|
58
|
+
priority_level: int
|
|
59
|
+
deficit_amount: int
|
|
60
|
+
Covered: ClassVar["FeePriority"]
|
|
61
|
+
ModifiableDeficit: ClassVar[Callable[[int], "FeePriority"]]
|
|
62
|
+
ImmutableDeficit: ClassVar[Callable[[int], "FeePriority"]]
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def covered() -> "FeePriority":
|
|
66
|
+
return FeePriority(0, 0)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def modifiable_deficit(amount: int) -> "FeePriority":
|
|
70
|
+
return FeePriority(1, amount)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def immutable_deficit(amount: int) -> "FeePriority":
|
|
74
|
+
return FeePriority(2, amount)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
FeePriority.Covered = FeePriority.covered()
|
|
78
|
+
FeePriority.ModifiableDeficit = staticmethod(FeePriority.modifiable_deficit)
|
|
79
|
+
FeePriority.ImmutableDeficit = staticmethod(FeePriority.immutable_deficit)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from algokit_common import PROGRAM_PAGE_SIZE
|
|
2
|
+
|
|
3
|
+
__all__ = ["calculate_extra_program_pages"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def calculate_extra_program_pages(approval: bytes | None, clear: bytes | None) -> int:
|
|
7
|
+
"""Calculate minimum number of extra_pages required for provided approval and clear programs."""
|
|
8
|
+
total = len(approval or b"") + len(clear or b"")
|
|
9
|
+
return max(0, (total - 1) // PROGRAM_PAGE_SIZE)
|