-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8c85ccf
commit ce23e15
Showing
3 changed files
with
168 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[pytest] | ||
markers = | ||
serial: marks tests requiring serial execution |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |