AVM Opcodes
The coverage file provides a comprehensive list of all opcodes and their respective types, categorized as Mockable, Emulated, or Native within the algorand-python-testing
package. This section highlights a subset of opcodes and types that typically require interaction with the test context manager.
Native
opcodes are assumed to function as they do in the Algorand Virtual Machine, given their stateless nature. If you encounter issues with any Native
opcodes, please raise an issue in the algorand-python-testing
repo or contribute a PR following the Contributing guide.
import algopyfrom algopy_testing import algopy_testing_context
# Create the context manager for snippets belowctx_manager = algopy_testing_context()
# Enter the contextcontext = ctx_manager.__enter__()
Implemented Types
These types are fully implemented in Python and behave identically to their AVM counterparts:
1. Cryptographic Operations
The following opcodes are demonstrated:
op.sha256
op.keccak256
op.ecdsa_verify
from algopy import op
# SHA256 hashdata = algopy.Bytes(b"Hello, World!")hashed = op.sha256(data)
# Keccak256 hashkeccak_hashed = op.keccak256(data)
# ECDSA verificationmessage_hash = bytes.fromhex("f809fd0aa0bb0f20b354c6b2f86ea751957a4e262a546bd716f34f69b9516ae1")sig_r = bytes.fromhex("18d96c7cda4bc14d06277534681ded8a94828eb731d8b842e0da8105408c83cf")sig_s = bytes.fromhex("7d33c61acf39cbb7a1d51c7126f1718116179adebd31618c4604a1f03b5c274a")pubkey_x = bytes.fromhex("f8140e3b2b92f7cbdc8196bc6baa9ce86cf15c18e8ad0145d50824e6fa890264")pubkey_y = bytes.fromhex("bd437b75d6f1db67155a95a0da4b41f2b6b3dc5d42f7db56238449e404a6c0a3")
result = op.ecdsa_verify(op.ECDSA.Secp256r1, message_hash, sig_r, sig_s, pubkey_x, pubkey_y)assert result
2. Arithmetic and Bitwise Operations
The following opcodes are demonstrated:
op.addw
op.bitlen
op.getbit
op.setbit_uint64
from algopy import op
# Addition with carryresult, carry = op.addw(algopy.UInt64(2**63), algopy.UInt64(2**63))
# Bitwise operationsvalue = algopy.UInt64(42)bit_length = op.bitlen(value)is_bit_set = op.getbit(value, 3)new_value = op.setbit_uint64(value, 2, 1)
For a comprehensive list of all opcodes and types, refer to the coverage page.
Emulated Types Requiring Transaction Context
These types necessitate interaction with the transaction context:
algopy.op.Global
from algopy import op
class MyContract(algopy.ARC4Contract): @algopy.arc4.abimethod def check_globals(self) -> algopy.UInt64: return op.Global.min_txn_fee + op.Global.min_balance
... # setup context (below assumes available under 'ctx' variable)
context.ledger.patch_global_fields( min_txn_fee=algopy.UInt64(1000), min_balance=algopy.UInt64(100000))
contract = MyContract()result = contract.check_globals()assert result == algopy.UInt64(101000)
algopy.op.Txn
from algopy import op
class MyContract(algopy.ARC4Contract): @algopy.arc4.abimethod def check_txn_fields(self) -> algopy.arc4.Address: return algopy.arc4.Address(op.Txn.sender)
... # setup context (below assumes available under 'ctx' variable)
contract = MyContract()custom_sender = context.any.account()with context.txn.create_group(active_txn_overrides={"sender": custom_sender}): result = contract.check_txn_fields()assert result == custom_sender
algopy.op.AssetHoldingGet
from algopy import op
class AssetContract(algopy.ARC4Contract): @algopy.arc4.abimethod def check_asset_holding(self, account: algopy.Account, asset: algopy.Asset) -> algopy.UInt64: balance, _ = op.AssetHoldingGet.asset_balance(account, asset) return balance
... # setup context (below assumes available under 'ctx' variable)
asset = context.any.asset(total=algopy.UInt64(1000000))account = context.any.account(opted_asset_balances={asset.id: algopy.UInt64(5000)})contract = AssetContract()result = contract.check_asset_holding(account, asset)assert result == algopy.UInt64(5000)
algopy.op.AppGlobal
from algopy import op
class StateContract(algopy.ARC4Contract): @algopy.arc4.abimethod def set_and_get_state(self, key: algopy.Bytes, value: algopy.UInt64) -> algopy.UInt64: op.AppGlobal.put(key, value) return op.AppGlobal.get_uint64(key)
... # setup context (below assumes available under 'ctx' variable)
contract = StateContract()key, value = algopy.Bytes(b"test_key"), algopy.UInt64(42)result = contract.set_and_get_state(key, value)assert result == valuestored_value = context.ledger.get_global_state(contract, key)assert stored_value == 42
algopy.op.Block
from algopy import op
class BlockInfoContract(algopy.ARC4Contract): @algopy.arc4.abimethod def get_block_seed(self) -> algopy.Bytes: return op.Block.blk_seed(1000)
... # setup context (below assumes available under 'ctx' variable)
context.ledger.set_block(1000, seed=123456, timestamp=1625097600)contract = BlockInfoContract()seed = contract.get_block_seed()assert seed == algopy.op.itob(123456)
algopy.op.AcctParamsGet
from algopy import op
class AccountParamsContract(algopy.ARC4Contract): @algopy.arc4.abimethod def get_account_balance(self, account: algopy.Account) -> algopy.UInt64: balance, exists = op.AcctParamsGet.acct_balance(account) assert exists return balance
... # setup context (below assumes available under 'ctx' variable)
account = context.any.account(balance=algopy.UInt64(1000000))contract = AccountParamsContract()balance = contract.get_account_balance(account)assert balance == algopy.UInt64(1000000)
algopy.op.AppParamsGet
class AppParamsContract(algopy.ARC4Contract): @algopy.arc4.abimethod def get_app_creator(self, app_id: algopy.Application) -> algopy.arc4.Address: creator, exists = algopy.op.AppParamsGet.app_creator(app_id) assert exists return algopy.arc4.Address(creator)
... # setup context (below assumes available under 'ctx' variable)
contract = AppParamsContract()app = context.any.application()creator = contract.get_app_creator(app)assert creator == context.default_sender
algopy.op.AssetParamsGet
from algopy import op
class AssetParamsContract(algopy.ARC4Contract): @algopy.arc4.abimethod def get_asset_total(self, asset_id: algopy.UInt64) -> algopy.UInt64: total, exists = op.AssetParamsGet.asset_total(asset_id) assert exists return total
... # setup context (below assumes available under 'ctx' variable)
asset = context.any.asset(total=algopy.UInt64(1000000), decimals=algopy.UInt64(6))contract = AssetParamsContract()total = contract.get_asset_total(asset.id)assert total == algopy.UInt64(1000000)
algopy.op.Box
from algopy import op
class BoxStorageContract(algopy.ARC4Contract): @algopy.arc4.abimethod def store_and_retrieve(self, key: algopy.Bytes, value: algopy.Bytes) -> algopy.Bytes: op.Box.put(key, value) retrieved_value, exists = op.Box.get(key) assert exists return retrieved_value
... # setup context (below assumes available under 'ctx' variable)
contract = BoxStorageContract()key, value = algopy.Bytes(b"test_key"), algopy.Bytes(b"test_value")result = contract.store_and_retrieve(key, value)assert result == valuestored_value = context.ledger.get_box(contract, key)assert stored_value == value.value
Mockable Opcodes
These opcodes are mockable in algorand-python-testing
, allowing for controlled testing of complex operations:
algopy.compile_contract
from unittest.mock import patch, MagicMockimport algopy
mocked_response = MagicMock()mocked_response.local_bytes = algopy.UInt64(4)
class MockContract(algopy.Contract): ...
class ContractFactory(algopy.ARC4Contract): ...
@algopy.arc4.abimethod def compile_and_get_bytes(self) -> algopy.UInt64: contract_response = algopy.compile_contract(MockContract) return contract_response.local_bytes
... # setup context (below assumes available under 'ctx' variable)
contract = ContractFactory()with patch('algopy.compile_contract', return_value=mocked_response): assert contract.compile_and_get_bytes() == 4
algopy.arc4.abi_call
import unittestfrom unittest.mock import patch, MagicMockimport algopyimport typing
class MockAbiCall: def __call__( self, *args: typing.Any, **_kwargs: typing.Any ) -> tuple[typing.Any, typing.Any]: return ( algopy.arc4.UInt64(11), MagicMock(), )
def __getitem__(self, _item: object) -> typing.Self: return self
class MyContract(algopy.ARC4Contract): @algopy.arc4.abimethod def my_method(self, arg1: algopy.UInt64, arg2: algopy.UInt64) -> algopy.UInt64: return algopy.arc4.abi_call[algopy.arc4.UInt64]("my_other_method", arg1, arg2)[0].native
... # setup context (below assumes available under 'ctx' variable)
contract = MyContract()with patch('algopy.arc4.abi_call', MockAbiCall()): result = contract.my_method(algopy.UInt64(10), algopy.UInt64(1))assert result == 11
algopy.op.vrf_verify
from unittest.mock import patch, MagicMockimport algopy
def test_mock_vrf_verify(): mock_result = (algopy.Bytes(b'mock_output'), True) with patch('algopy.op.vrf_verify', return_value=mock_result) as mock_vrf_verify: result = algopy.op.vrf_verify( algopy.op.VrfVerify.VrfAlgorand, algopy.Bytes(b'proof'), algopy.Bytes(b'message'), algopy.Bytes(b'public_key') ) assert result == mock_result mock_vrf_verify.assert_called_once_with( algopy.op.VrfVerify.VrfAlgorand, algopy.Bytes(b'proof'), algopy.Bytes(b'message'), algopy.Bytes(b'public_key') )
test_mock_vrf_verify()
algopy.op.EllipticCurve
from unittest.mock import patch, MagicMockimport algopy
def test_mock_elliptic_curve_add(): mock_result = algopy.Bytes(b'result') with patch('algopy.op.EllipticCurve.add', return_value=mock_result) as mock_add: result = algopy.op.EllipticCurve.add( algopy.op.EC.BN254g1, algopy.Bytes(b'a'), algopy.Bytes(b'b') ) assert result == mock_result mock_add.assert_called_once_with( algopy.op.EC.BN254g1, algopy.Bytes(b'a'), algopy.Bytes(b'b'), )
test_mock_elliptic_curve_add()
These examples demonstrate how to mock key mockable opcodes in algorand-python-testing
. Use similar techniques (in your preferred testing framework) for other mockable opcodes like algopy.compile_logicsig
, algopy.arc4.arc4_create
, and algopy.arc4.arc4_update
.
Mocking these opcodes allows you to:
- Control complex operations’ behavior not covered by implemented and emulated types.
- Test edge cases and error conditions.
- Isolate contract logic from external dependencies.
ctx_manager.__exit__(None, None, None)