Skip to content

chore: refreshing todos #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/algopy_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
from algopy_testing.primitives import BigUInt, Bytes, String, UInt64
from algopy_testing.state import Box, BoxMap, BoxRef, GlobalState, LocalState

# TODO: clean up and ensure only algopy_testing namespace specific user facing abstractions
# are exposed Only keep the _value_generators, ledger_context, txn_context,
# context, and arc4_prexif from utils (make utils private)
__all__ = [
"ARC4Contract",
"ARC4ValueGenerator",
Expand Down
3 changes: 2 additions & 1 deletion src/algopy_testing/_context_helpers/ledger_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self) -> None:
self.account_data = defaultdict[str, AccountContextData](get_empty_account)
self.application_data: dict[int, ApplicationContextData] = {}
self.asset_data: dict[int, AssetFields] = {}
# TODO: move boxes onto application data
# TODO: 1.0 move boxes onto application data
self.boxes: dict[bytes, bytes] = {}
self.blocks: dict[int, dict[str, int]] = {}
self.global_fields: GlobalFields = get_default_global_fields()
Expand Down Expand Up @@ -85,6 +85,7 @@ def update_application(

self.application_data[app_id].fields.update(application_fields)

# TODO: 1.0 add app ids, access the boxes from application data
def get_box(self, name: algopy.Bytes | bytes) -> bytes:
name_bytes = name if isinstance(name, bytes) else name.value
return self.boxes.get(name_bytes, b"")
Expand Down
2 changes: 1 addition & 1 deletion src/algopy_testing/_value_generators/avm.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def asset(
if asset_id and asset_id in lazy_context.ledger.asset_data:
raise ValueError("Asset with such ID already exists in testing context!")

# TODO: ensure passed fields are valid names and types
# TODO: 1.0 ensure passed fields are valid names and types
new_asset = algopy.Asset(asset_id or next(lazy_context.ledger.asset_id))
default_asset_fields = {
"total": lazy_context.any.uint64(),
Expand Down
1 change: 0 additions & 1 deletion src/algopy_testing/_value_generators/txn.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ def transaction(
return self._new_gtxn(gtxn.Transaction, **fields)

def _new_gtxn(self, txn_type: type[_TGlobalTxn], **fields: object) -> _TGlobalTxn:
# TODO: check reference types are known?
fields.setdefault("type", txn_type.type_enum)
fields.setdefault("sender", lazy_context.value.default_sender)

Expand Down
5 changes: 1 addition & 4 deletions src/algopy_testing/arc4.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,6 @@ def __init__(
bytes_value = int_to_bytes(value, self.type_info.max_bytes_len)
self._value = as_bytes(bytes_value)

# ~~~ https://docs.python.org/3/reference/datamodel.html#basic-customization ~~~
# TODO: mypy suggests due to Liskov below should be other: object
# need to consider ramifications here, ignoring it for now
def __eq__( # type: ignore[override]
self,
other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
Expand Down Expand Up @@ -1031,7 +1028,7 @@ def _tuple_type_from_struct(struct: type[Struct]) -> type[Tuple]: # type: ignor
class Struct(metaclass=_StructMeta):
"""Base class for ARC4 Struct types."""

type_info: typing.ClassVar[_TypeInfo] # TODO: this could clash with user values
type_info: typing.ClassVar[_TypeInfo] # TODO: 1.0 this could clash with user values

def __init_subclass__(cls) -> None:
dataclasses.dataclass(cls)
Expand Down
1 change: 0 additions & 1 deletion src/algopy_testing/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def __init__(
) -> None:
import algopy

# TODO: remove direct reads of data mappings outside of context_storage
self._default_sender = algopy.Account(
default_sender or algosdk.account.generate_account()[1]
)
Expand Down
10 changes: 3 additions & 7 deletions src/algopy_testing/decorators/arc4.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def get_arc4_metadata(fn: object) -> MethodMetadata:
def get_ordered_args(
_fn: Callable[..., typing.Any], app_args: list[typing.Any], kwargs: dict[str, typing.Any]
) -> list[typing.Any]:
# TODO: order kwargs correctly based on fn signature
# TODO: 1.0 order kwargs correctly based on fn signature
return [*app_args, *kwargs.values()]


Expand Down Expand Up @@ -140,8 +140,6 @@ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
app_id = contract.__app_id__

context = lazy_context.value
# TODO: does contract need to be active here?
# TODO: handle custom txn groups
ordered_args = get_ordered_args(fn, app_args, kwargs)
assert metadata.arc4_signature is not None, "expected abimethod"
txns = create_abimethod_txns(
Expand All @@ -152,7 +150,7 @@ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
with context.txn._maybe_implicit_txn_group(txns):
check_routing_conditions(app_id, metadata)
result = fn(*args, **kwargs)
# TODO: add result along with ARC4 log prefix to application logs?
# TODO: 1.0 add result along with ARC4 log prefix to application logs?
return result

return wrapper
Expand Down Expand Up @@ -202,7 +200,6 @@ def create_baremethod_txns(app_id: int) -> list[algopy.gtxn.TransactionBase]:
contract_app = lazy_context.ledger.get_application(app_id)
txn_fields.setdefault("app_id", contract_app)

# TODO: fill out other fields where possible (see abimethod)
txn_fields.setdefault(
"approval_program_pages", [algopy_testing.Bytes(ALWAYS_APPROVE_TEAL_PROGRAM)]
)
Expand Down Expand Up @@ -253,7 +250,7 @@ def _extract_arrays_from_args(
case _ as maybe_native:
app_args.append(algopy_testing.arc4.native_value_to_arc4(maybe_native).bytes)
if len(app_args) > 16:
# TODO: pack extra args into an ARC4 tuple
# TODO:1.0 pack extra args into an ARC4 tuple
raise NotImplementedError
return _TxnArrays(
txns=txns,
Expand Down Expand Up @@ -374,7 +371,6 @@ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
assert isinstance(contract, algopy_testing.ARC4Contract), "expected ARC4 contract"
assert fn is not None, "expected function"

# TODO: handle custom txn groups
txns = create_baremethod_txns(contract.__app_id__)

with lazy_context.txn._maybe_implicit_txn_group(txns):
Expand Down
3 changes: 2 additions & 1 deletion src/algopy_testing/models/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class ApplicationContextData:
)
is_creating: bool = False
contract: Contract | None = None
# TODO: 1.0 add getter to ledger context, get_global_state, get_local_state, get_box

def get_global_state(self, key: algopy.Bytes | bytes) -> StateValueType:
return self.global_state[as_bytes(key)]
Expand Down Expand Up @@ -99,7 +100,7 @@ def fields(self) -> ApplicationFields:
def __getattr__(self, name: str) -> typing.Any:
if name in inspect.get_annotations(ApplicationFields):
value = self.fields.get(name)
# TODO: ensure reasonable default values are present (like account does)
# TODO: 1.0 ensure reasonable default values are present (like account does)
if value is None:
raise ValueError(
f"The Application value '{name}' has not been defined on the test context. "
Expand Down
8 changes: 4 additions & 4 deletions src/algopy_testing/models/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __call__(cls, *args: typing.Any, **kwargs: dict[str, typing.Any]) -> object:
fields = lazy_context.get_txn_op_fields()
fields["app_id"] = app_ref

# TODO: provide app_id during instantiation without requiring a txn
# TODO: 1.0 provide app_id during instantiation without requiring a txn
txn = context.any.txn.application_call(**fields)
with context.txn.create_group([txn]):
instance = super().__call__(*args, **kwargs)
Expand Down Expand Up @@ -91,7 +91,7 @@ def __init_subclass__(
) = None,
state_totals: StateTotals | None = None,
):
# TODO: check storing these on cls does not interfere with instances
# TODO: 1.0 add test : check storing these on cls does not interfere with instances
# by having a contract with _name, _scratch_slots and _state_totals attributes
cls._name = name or cls.__name__
cls._scratch_slots = scratch_slots
Expand All @@ -113,7 +113,7 @@ def set_active_contract(
*args: typing.Any, **kwargs: dict[str, typing.Any]
) -> typing.Any:
context = lazy_context.value
# TODO: this should populate the app txn as much as possible like abimethod does
# TODO: 1.0 should populate the app txn as much as possible like abimethod does
app = context.ledger.get_application(_get_self_or_active_app_id(self))
txns = [context.any.txn.application_call(app_id=app)]
with context.txn._maybe_implicit_txn_group(txns):
Expand Down Expand Up @@ -207,7 +207,7 @@ def _get_state_totals(contract: Contract, cls_state_totals: StateTotals) -> _Sta
else:
local_bytes += 1

# TODO: add tests for state overrides
# TODO: 1.0 add tests for state overrides
# apply any cls specific overrides
if cls_state_totals.global_uints is not None:
global_uints = cls_state_totals.global_uints
Expand Down
7 changes: 3 additions & 4 deletions src/algopy_testing/models/txn_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ class ApplicationCallFields(TransactionBaseFields, total=False):
accounts: Sequence[algopy.Account]
assets: Sequence[algopy.Asset]
apps: Sequence[algopy.Application]
# TODO: when storing these pages values, combine into one bytes and then
# "chop" into 4096 length pieces
# TODO: 1.0 when storing these pages values, combine into one bytes and then
# "chop" into 4096 length pieces. Covered in ITxns, ensure Txns use the same method
approval_program: Sequence[algopy.Bytes]
clear_state_program: Sequence[algopy.Bytes]

Expand All @@ -104,7 +104,6 @@ class TransactionFields( # type: ignore[misc]
type: algopy.TransactionType


# TODO: can this easily be derived from annotations?
_FIELD_TYPES = {
"sender": Account,
"rekey_to": Account,
Expand Down Expand Up @@ -201,7 +200,7 @@ def sender(self) -> algopy.Account:
return self.fields["sender"] # type: ignore[return-value]

@property
def txn_id(self) -> algopy.Bytes: # TODO: txn_id or tx_id
def txn_id(self) -> algopy.Bytes:
return self.fields["txn_id"] # type: ignore[return-value]

@property
Expand Down
2 changes: 1 addition & 1 deletion src/algopy_testing/op/global_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def latest_timestamp(self) -> algopy.UInt64:
try:
return self._fields["latest_timestamp"]
except KeyError:
# TODO: cache this for the active txn? this value shouldn't change during execution
# TODO: 1.0 Construct this while setting default values rather than here.
return UInt64(int(time.time()))

@property
Expand Down
5 changes: 1 addition & 4 deletions src/algopy_testing/op/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def gaid(a: algopy.UInt64 | int, /) -> algopy.Application:


def balance(a: algopy.Account | algopy.UInt64 | int, /) -> algopy.UInt64:
# TODO: add tests
# TODO: 1.0 add tests
active_txn = lazy_context.active_group.active_txn

account = _get_account(a)
Expand All @@ -112,8 +112,6 @@ def min_balance(a: algopy.Account | algopy.UInt64 | int, /) -> algopy.UInt64:

def exit(a: UInt64 | int, /) -> typing.Never: # noqa: A001
value = UInt64(a) if isinstance(a, int) else a
# TODO: in the docs indicate this method as mockable, as the semantics don't really
# work very well with Python, so leave it up the user to decide what should happen
raise NotImplementedError(f"exit with value: {value}")


Expand Down Expand Up @@ -383,7 +381,6 @@ def put(


def arg(a: UInt64 | int, /) -> Bytes:
# TODO: read from active group
return lazy_context.value._active_lsig_args[int(a)]


Expand Down
8 changes: 8 additions & 0 deletions src/algopy_testing/state/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def value(self) -> _TValue:
context = get_test_context()
if not context.ledger.box_exists(self.key):
raise RuntimeError("Box has not been created")
# TODO: 1.0 will need to use a proxy here too for mutable types
return cast_from_bytes(self._type, context.ledger.get_box(self.key))

@value.setter
Expand Down Expand Up @@ -440,6 +441,13 @@ def _full_key(self, key: _TKey) -> algopy.Bytes:
return self.key_prefix + cast_to_bytes(key)


# TODO: 1.0 using this proxy will mean other parts of the library that do isinstance will not be
# correct. To solve this need to do the following
# 1.) only use the proxy if the value is mutable, currently this means only
# ARC Arrays, Tuples and structs should need this proxy
# 2.) modify the metaclass of the above types to ensure they still pass the appropriate
# isinstance checks, when a ProxyValue is being used
# see https://docs.python.org/3.12/reference/datamodel.html#customizing-instance-and-subclass-checks
class _ProxyValue:
"""Allows mutating attributes of objects retrieved from a BoxMap."""

Expand Down
1 change: 0 additions & 1 deletion src/algopy_testing/state/local_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def __init__(
case _:
raise ValueError("Key must be bytes or str")
self.description = description
self.app_id = 0 # TODO: set from contract instance

@property
def key(self) -> algopy.Bytes:
Expand Down
1 change: 0 additions & 1 deletion src/algopy_testing/state/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ def serialize(value: _TValue) -> SerializableValue:


def deserialize(typ: type[_TValue], value: SerializableValue) -> _TValue:
# TODO: handle arc4 instances that require a _TypeInfo
if issubclass(typ, bool):
return value != 0 # type: ignore[return-value]
elif issubclass(typ, UInt64 | Bytes):
Expand Down
2 changes: 1 addition & 1 deletion tests/arc4/test_arc4_method_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
UInt8Array,
)

# TODO: execute this on AVM too
# TODO: 1.0 execute this on AVM too


@pytest.fixture()
Expand Down
8 changes: 4 additions & 4 deletions tests/test_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def test_asset_params_get(

avm_result = get_state_asset_params_avm_result(method_name, a=dummy_asset, suggested_params=sp)
mock_result = getattr(mock_contract, method_name)(mock_asset)
# TODO: add separate tests by foreign array index
# TODO: 1.0 add separate tests by foreign array index

expected = expected_value(dummy_account) if callable(expected_value) else expected_value
expected = (
Expand Down Expand Up @@ -555,7 +555,7 @@ def test_app_params_get(
contract_method = getattr(contract, method_name)
result = contract_method(app)

# TODO: add alternate tests for testing by index
# TODO: 1.0 add alternate tests for testing by index
assert avm_result == result
if expected_value is not None:
assert avm_result == expected_value
Expand Down Expand Up @@ -618,7 +618,7 @@ def test_acct_params_get(
assert mock_result == 100_100_000 # assert it returns the value set in test context
mock_result = avm_result

# TODO: add alternate tests for testing by index
# TODO: 1.0 add alternate tests for testing by index
assert mock_result == avm_result

if expected_value is not None:
Expand Down Expand Up @@ -943,7 +943,7 @@ def test_itxn_ops(context: AlgopyTestContext) -> None:
appl_itxn = itxn_group.application_call(0)
pay_itxn = itxn_group.payment(1)

# TODO: also test other array fields, apps, accounts, applications, assets
# TODO: 1.0 also test other array fields, apps, accounts, applications, assets
assert appl_itxn.approval_program == algopy.Bytes.from_hex("068101068101")
assert appl_itxn.clear_state_program == algopy.Bytes.from_hex("068101")
approval_pages = [
Expand Down