State Management
algorand-python-testing provides tools to test state-related abstractions in Algorand smart contracts. This guide covers global state, local state, boxes, and scratch space management.
Global State
Section titled “Global State”Global state is represented as instance attributes on algopy.Contract and algopy.ARC4Contract classes.
import algopy
class MyContract(algopy.ARC4Contract): def __init__(self): self.state_a = algopy.GlobalState(algopy.UInt64, key="global_uint64") self.state_b = algopy.UInt64(1)
# In your testcontract = MyContract()contract.state_a.value = algopy.UInt64(10)contract.state_b.value = algopy.UInt64(20)Global Map
Section titled “Global Map”GlobalMap allows contracts to store collections of key-value pairs in global state, using a common key type and value type with an automatically derived (or explicit) key prefix.
class GlobalMapContract(algopy.ARC4Contract): def __init__(self): # Implicit key prefix (derived from attribute name "scores") self.scores = algopy.GlobalMap(algopy.Bytes, algopy.arc4.UInt64) # Explicit key prefix self.labels = algopy.GlobalMap(algopy.Bytes, algopy.arc4.String, key_prefix="my_labels")
# In your testcontract = GlobalMapContract()
# Set and get valuescontract.scores[algopy.Bytes(b"item_a")] = algopy.arc4.UInt64(100)assert contract.scores[algopy.Bytes(b"item_a")] == algopy.arc4.UInt64(100)
# Check existenceassert algopy.Bytes(b"item_a") in contract.scoresassert algopy.Bytes(b"item_b") not in contract.scores
# Get with defaultresult = contract.scores.get(algopy.Bytes(b"item_b"), default=algopy.arc4.UInt64(0))assert result == algopy.arc4.UInt64(0)
# Maybe (returns value and existence flag)value, exists = contract.scores.maybe(algopy.Bytes(b"item_a"))assert exists
# Deletedel contract.scores[algopy.Bytes(b"item_a")]assert algopy.Bytes(b"item_a") not in contract.scoresLocal State
Section titled “Local State”Local state is defined similarly to global state, but accessed using account addresses as keys.
import algopy
class MyContract(algopy.ARC4Contract): def __init__(self): self.local_state_a = algopy.LocalState(algopy.UInt64, key="state_a")
# In your testcontract = MyContract()account = context.any.account()contract.local_state_a[account] = algopy.UInt64(10)Local Map
Section titled “Local Map”LocalMap is similar to GlobalMap but stores per-account state. Values are accessed using an (account, key) tuple.
class LocalMapContract(algopy.ARC4Contract): def __init__(self): self.balances = algopy.LocalMap(algopy.Bytes, algopy.arc4.UInt64)
# In your testcontract = LocalMapContract()account = context.any.account()
# Set and get values using (account, key) tuplecontract.balances[account, algopy.Bytes(b"item_a")] = algopy.arc4.UInt64(500)assert contract.balances[account, algopy.Bytes(b"item_a")] == algopy.arc4.UInt64(500)
# Check existenceassert (account, algopy.Bytes(b"item_a")) in contract.balancesassert (account, algopy.Bytes(b"item_b")) not in contract.balances
# Get with defaultresult = contract.balances.get(account, algopy.Bytes(b"item_b"), default=algopy.arc4.UInt64(0))assert result == algopy.arc4.UInt64(0)
# Maybevalue, exists = contract.balances.maybe(account, algopy.Bytes(b"item_a"))assert exists
# Deletedel contract.balances[account, algopy.Bytes(b"item_a")]assert (account, algopy.Bytes(b"item_a")) not in contract.balances
# Different accounts have independent stateaccount2 = context.any.account()contract.balances[account, algopy.Bytes(b"item_a")] = algopy.arc4.UInt64(100)contract.balances[account2, algopy.Bytes(b"item_a")] = algopy.arc4.UInt64(200)assert contract.balances[account, algopy.Bytes(b"item_a")] == algopy.arc4.UInt64(100)assert contract.balances[account2, algopy.Bytes(b"item_a")] == algopy.arc4.UInt64(200)The framework supports various Box abstractions available in algorand-python.
import algopyfrom algopy import opfrom algopy import arc4
class MyContract(algopy.ARC4Contract): def __init__(self): self.box_map = algopy.BoxMap(algopy.Bytes, algopy.UInt64)
@algopy.arc4.abimethod() def some_method(self, key_a: algopy.Bytes, key_b: algopy.Bytes, key_c: algopy.Bytes) -> None: self.box = algopy.Box(algopy.UInt64, key=key_a) self.box.value = algopy.UInt64(1) self.box_map[key_b] = algopy.UInt64(1) self.box_map[key_c] = algopy.UInt64(2)
# In your testcontract = MyContract()key_a = b"key_a"key_b = b"key_b"key_c = b"key_c"
contract.some_method(algopy.Bytes(key_a), algopy.Bytes(key_b), algopy.Bytes(key_c))
# Access boxesbox_content = context.ledger.get_box(contract, key_a)assert context.ledger.box_exists(contract, key_a)
# Set box content manuallywith context.txn.create_group(): context.ledger.set_box(contract, key_a, algopy.op.itob(algopy.UInt64(1)))Scratch Space
Section titled “Scratch Space”Scratch space is represented as a list of 256 slots for each transaction.
import algopyfrom algopy import op
class MyContract(algopy.Contract, scratch_slots=(1, 2, algopy.urange(3, 20))): def approval_program(self): algopy.op.Scratch.store(1, algopy.UInt64(5)) assert algopy.op.Scratch.load_uint64(1) == algopy.UInt64(5) return True
# In your testcontract = MyContract()result = contract.approval_program()
assert resultscratch_space = context.txn.last_group.get_scratch_space()assert scratch_space[1] == algopy.UInt64(5)For more detailed information, explore the example contracts in the examples/ directory, the coverage page, and the API documentation.