Skip to content

Commit

Permalink
Add graviton tests for OpUp (#607)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldiamant committed Dec 12, 2022
1 parent 8c85ccf commit ce23e15
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ algod-stop:
docker compose stop algod

integration-run:
pytest -n $(NUM_PROCS) --durations=10 -sv tests/integration
pytest -n $(NUM_PROCS) --durations=10 -sv tests/integration -m "not serial"
pytest --durations=10 -sv tests/integration -m serial

test-integration: integration-run

Expand Down
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
markers =
serial: marks tests requiring serial execution
163 changes: 163 additions & 0 deletions tests/integration/opup_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import graviton.models
import pytest
import math

from typing import cast
import pyteal as pt
from tests.blackbox import (
Blackbox,
BlackboxWrapper,
PyTealDryRunExecutor,
)
import tests
from graviton.blackbox import DryRunExecutor, DryRunInspector, ExecutionMode

from algosdk.v2client.models import Account
import algosdk


def _dryrun(
bw: BlackboxWrapper,
sp: algosdk.future.transaction.SuggestedParams,
accounts: list[Account],
) -> DryRunInspector:
e = PyTealDryRunExecutor(bw, pt.Mode.Application)
return DryRunExecutor.execute_one_dryrun(
tests.blackbox.algod_with_assertion(),
e.compile(pt.compiler.MAX_PROGRAM_VERSION),
[],
ExecutionMode.Application,
e.abi_argument_types(),
e.abi_return_type(),
txn_params=DryRunExecutor.transaction_params(
sender=graviton.models.ZERO_ADDRESS,
sp=sp,
index=DryRunExecutor.EXISTING_APP_CALL,
on_complete=algosdk.future.transaction.OnComplete.NoOpOC,
),
accounts=accounts,
)


_application_opcode_budget = 700


@pytest.mark.parametrize("source", pt.OpUpFeeSource)
@pytest.mark.parametrize("inner_txn_count", range(1, 5))
@pytest.mark.parametrize("with_funding", [True, False])
@pytest.mark.serial # Serial due to reused account + application state
def test_opup_maximize_budget(
source: pt.OpUpFeeSource, inner_txn_count: int, with_funding: bool
):
innerTxnFeeMicroAlgos = inner_txn_count * algosdk.constants.min_txn_fee

@Blackbox(input_types=[])
@pt.Subroutine(pt.TealType.uint64)
def maximize_budget():
return pt.Seq(
pt.OpUp(mode=pt.OpUpMode.OnCall).maximize_budget(
pt.Int(innerTxnFeeMicroAlgos), source
),
pt.Global.opcode_budget(),
)

if with_funding:
accounts = (
[
Account(
address=algosdk.logic.get_application_address(
DryRunExecutor.EXISTING_APP_CALL
),
status="Offline",
amount=innerTxnFeeMicroAlgos,
amount_without_pending_rewards=innerTxnFeeMicroAlgos,
)
]
if source == pt.OpUpFeeSource.AppAccount
else []
)

sp = DryRunExecutor.SUGGESTED_PARAMS
sp.fee = (
innerTxnFeeMicroAlgos + algosdk.constants.min_txn_fee
if source == pt.OpUpFeeSource.GroupCredit
else sp.fee
)

result = _dryrun(maximize_budget, sp, accounts)

assert result.passed()
assert result.budget_added() == _application_opcode_budget * inner_txn_count
else:
# Withholding account and/or transaction fee funding fails the
# transaction.
sp = DryRunExecutor.SUGGESTED_PARAMS
sp.flat_fee = True
sp.fee = algosdk.constants.min_txn_fee
result = _dryrun(maximize_budget, DryRunExecutor.SUGGESTED_PARAMS, [])
assert not result.passed()


@pytest.mark.parametrize("source", [f for f in pt.OpUpFeeSource])
@pytest.mark.parametrize("budget_added", range(1_000, 20_000, 2_500))
@pytest.mark.parametrize("with_funding", [True, False])
@pytest.mark.serial # Serial due to reused account + application state
def test_opup_ensure_budget(
source: pt.OpUpFeeSource, budget_added: int, with_funding: bool
):
inner_txn_count = math.ceil(budget_added / _application_opcode_budget)
innerTxnFeeMicroAlgos = (
inner_txn_count * algosdk.constants.min_txn_fee + algosdk.constants.min_txn_fee
)
dryrun_starting_budget = 70_000 # https://github.com/algorand/go-algorand/blob/3a5e5847bebc21bfcae1f5fe056a78067b16c781/daemon/algod/api/server/v2/dryrun.go#L408

@Blackbox(input_types=[])
@pt.Subroutine(pt.TealType.uint64)
def ensure_budget():
return pt.Seq(
pt.OpUp(mode=pt.OpUpMode.OnCall).ensure_budget(
pt.Int(dryrun_starting_budget + budget_added), source
),
pt.Global.opcode_budget(),
)

if with_funding:
accounts = (
[
Account(
address=algosdk.logic.get_application_address(
DryRunExecutor.EXISTING_APP_CALL
),
status="Offline",
amount=innerTxnFeeMicroAlgos,
amount_without_pending_rewards=innerTxnFeeMicroAlgos,
)
]
if source == pt.OpUpFeeSource.AppAccount
else []
)

sp = DryRunExecutor.SUGGESTED_PARAMS
sp.fee = (
innerTxnFeeMicroAlgos + algosdk.constants.min_txn_fee
if source == pt.OpUpFeeSource.GroupCredit
else sp.fee
)

result = _dryrun(ensure_budget, sp, accounts)

assert result.passed(), result.report()
# Since ensure_budget guarantees _at least_ the required budget, the
# assertions confirm minimum expected budget added without significantly
# overshooting the mark.
actual = cast(int, result.budget_added())
threshold = _application_opcode_budget * inner_txn_count
assert threshold <= actual <= threshold + _application_opcode_budget
else:
# Withholding account and/or transaction fee funding fails the
# transaction.
sp = DryRunExecutor.SUGGESTED_PARAMS
sp.flat_fee = True
sp.fee = algosdk.constants.min_txn_fee
result = _dryrun(ensure_budget, DryRunExecutor.SUGGESTED_PARAMS, [])
assert not result.passed()

0 comments on commit ce23e15

Please sign in to comment.