def blackbox_pyteal_example1(): # Example 1: Using blackbox_pyteal for a simple test of both an app and logic sig: from graviton.blackbox import DryRunEncoder from pyteal import Int, Mode, Subroutine, TealType from tests.blackbox import Blackbox @Blackbox(input_types=[TealType.uint64]) @Subroutine(TealType.uint64) def square(x): return x**Int(2) # provide args for evaluation (will compute x^2) x = 9 args = [x] # evaluate the programs app_result = PyTealDryRunExecutor(square, Mode.Application).dryrun(args) lsig_result = PyTealDryRunExecutor(square, Mode.Signature).dryrun(args) # check to see that x^2 is at the top of the stack as expected assert app_result.stack_top() == x**2, app_result.report( args, "stack_top() gave unexpected results for app") assert lsig_result.stack_top() == x**2, lsig_result.report( args, "stack_top() gave unexpected results for lsig") # check to see that itob of x^2 has been logged (only for the app case) assert app_result.last_log() == DryRunEncoder.hex(x**2), app_result.report( args, "last_log() gave unexpected results from app")
def test_recover(): @Blackbox(input_types=[]) @Subroutine(TealType.uint64) def recover(): return EcdsaRecover( EcdsaCurve.Secp256k1, Sha512_256(Bytes("testdata")), Int(1), Bytes( "base16", "cabed943e1403fb93b388174c59a52c759b321855f2d7c4fcc23c99a8a6dce79", ), Bytes( "base16", "56192820dde344c32f81450db05e51c6a6f45a2a2db229f657d2c040baf31537", ), ).outputReducer(lambda x, y: And( x == Bytes( "base16", "71539e0c7a6902a3f5413d6e28a455b2a14316fcf0f6b21193343b3b9d455053", ), y == Bytes( "base16", "fa49ccd95795c7c9a447fdeee83a2193472507a4e41a47e0d50eeeb547b74c51", ), )) args = [] app_result = PyTealDryRunExecutor(recover, Mode.Application).dryrun( args, compiler_version=5) assert app_result.stack_top() == 1, app_result.report( args, "stack_top() is not equal to 1, indicating ecdsa verification failed.")
def test_decompress(): @Blackbox(input_types=[]) @Subroutine(TealType.uint64) def decompress(): return EcdsaDecompress( EcdsaCurve.Secp256k1, Bytes( "base16", "03bd83d54f6a799d05b496653b64bc933e17a898cda4793fe662d50645ecc977d1", ), ).outputReducer(lambda x, y: And( x == Bytes( "base16", "bd83d54f6a799d05b496653b64bc933e17a898cda4793fe662d50645ecc977d1", ), y == Bytes( "base16", "d4f3063a1ffca4139ea921b5696a6597640289175afece3bc38217a29d6270f9", ), )) args = [] app_result = PyTealDryRunExecutor(decompress, Mode.Application).dryrun( args, compiler_version=5) assert app_result.stack_top() == 1, app_result.report( args, "stack_top() is not equal to 1, indicating ecdsa verification failed.")
def blackbox_pyteal_named_tupleness_test(): from typing import Literal as L from tests.blackbox import Blackbox from pyteal import ( Seq, abi, Subroutine, TealType, Return, And, Mode, ) class NamedTupleExample(abi.NamedTuple): a: abi.Field[abi.Bool] b: abi.Field[abi.Address] c: abi.Field[abi.Tuple2[abi.Uint64, abi.Bool]] d: abi.Field[abi.StaticArray[abi.Byte, L[10]]] e: abi.Field[abi.StaticArray[abi.Bool, L[4]]] f: abi.Field[abi.Uint64] @Blackbox(input_types=[None] * 6) @Subroutine(TealType.uint64) def named_tuple_field_access( a_0: abi.Bool, a_1: abi.Address, a_2: abi.Tuple2[abi.Uint64, abi.Bool], a_3: abi.StaticArray[abi.Byte, L[10]], a_4: abi.StaticArray[abi.Bool, L[4]], a_5: abi.Uint64, ): return Seq( (v_tuple := NamedTupleExample()).set(a_0, a_1, a_2, a_3, a_4, a_5), (v_a := abi.Bool()).set(v_tuple.a), (v_b := abi.Address()).set(v_tuple.b), (v_c := abi.make(abi.Tuple2[abi.Uint64, abi.Bool])).set(v_tuple.c), (v_d := abi.make(abi.StaticArray[abi.Byte, L[10]])).set(v_tuple.d), (v_e := abi.make(abi.StaticArray[abi.Bool, L[4]])).set(v_tuple.e), (v_f := abi.Uint64()).set(v_tuple.f), Return( And( a_0.get() == v_a.get(), a_1.get() == v_b.get(), a_2.encode() == v_c.encode(), a_3.encode() == v_d.encode(), a_4.encode() == v_e.encode(), a_5.get() == v_f.get(), )), ) lsig_pytealer = PyTealDryRunExecutor(named_tuple_field_access, Mode.Signature) args = (False, b"1" * 32, (0, False), b"0" * 10, [True] * 4, 0) inspector = lsig_pytealer.dryrun(args) assert inspector.stack_top() == 1 assert inspector.passed()
def test_abi_blackbox_pyteal(subr_abi: Tuple[BlackboxWrapper, Optional[pt.ast.abi.BaseType]], mode: pt.Mode): subr, abi_return_type = subr_abi name = f"{'app' if mode == pt.Mode.Application else 'lsig'}_{subr.name()}" print(f"Case {subr.name()=}, {abi_return_type=}, {mode=} ------> {name=}") pdre = PyTealDryRunExecutor(subr, mode) assert pdre.is_abi(), "should be an ABI subroutine" arg_types = pdre.abi_argument_types() if subr.name() != "fn_1tt_arg_uint64_ret": assert not arg_types or any( arg_types), "abi_argument_types() should have had some abi info" if abi_return_type: expected_sdk_return_type = pt.abi.algosdk_from_type_spec( abi_return_type.type_spec()) assert expected_sdk_return_type == pdre.abi_return_type() else: assert pdre.abi_return_type() is None compiled = pdre.compile(version=6) tealdir = GENERATED / "abi" tealdir.mkdir(parents=True, exist_ok=True) save_to = tealdir / (name + ".teal") with open(save_to, "w") as f: f.write(compiled) assert_teal_as_expected(save_to, FIXTURES / "abi" / (name + ".teal"))
def blackbox_pyteal_example2(): # Example 2: Using blackbox_pyteal to make 400 assertions and generate a CSV report with 400 dryrun rows from itertools import product import math from pathlib import Path import random from graviton.blackbox import DryRunInspector from pyteal import ( For, If, Int, Mod, Mode, ScratchVar, Seq, Subroutine, TealType, ) from tests.blackbox import Blackbox # GCD via the Euclidean Algorithm (iterative version): @Blackbox(input_types=[TealType.uint64, TealType.uint64]) @Subroutine(TealType.uint64) def euclid(x, y): a = ScratchVar(TealType.uint64) b = ScratchVar(TealType.uint64) tmp = ScratchVar(TealType.uint64) start = If(x < y, Seq(a.store(y), b.store(x)), Seq(a.store(x), b.store(y))) cond = b.load() > Int(0) step = Seq(tmp.store(b.load()), b.store(Mod(a.load(), b.load())), a.store(tmp.load())) return Seq(For(start, cond, step).Do(Seq()), a.load()) # generate a report with 400 = 20*20 dry run rows: N = 20 inputs = list( product( tuple(random.randint(0, 1000) for _ in range(N)), tuple(random.randint(0, 1000) for _ in range(N)), )) # assert that each result is that same as what Python's math.gcd() computes inspectors = PyTealDryRunExecutor( euclid, Mode.Application).dryrun_on_sequence(inputs) for i, result in enumerate(inspectors): args = inputs[i] assert result.stack_top() == math.gcd(*args), result.report( args, f"failed for {args}") # save the CSV to ...current working directory.../euclid.csv euclid_csv = DryRunInspector.csv_report(inputs, inspectors) with open(Path.cwd() / "euclid.csv", "w") as f: f.write(euclid_csv)
def test_blackbox_pyteal(subr: BlackboxWrapper, mode: pt.Mode): is_app = mode == pt.Mode.Application name = f"{'app' if is_app else 'lsig'}_{subr.name()}" compiled = PyTealDryRunExecutor(subr, mode).compile(version=6) tealdir = GENERATED / "blackbox" tealdir.mkdir(parents=True, exist_ok=True) save_to = tealdir / (name + ".teal") with open(save_to, "w") as f: f.write(compiled) assert_teal_as_expected(save_to, FIXTURES / "blackbox" / (name + ".teal"))
def test_PyTealBlackboxExecutor_is_abi(mode: pt.Mode, fn: BlackboxWrapper, expected_is_abi: bool): p = PyTealDryRunExecutor(fn, mode) assert p.is_abi() == expected_is_abi if expected_is_abi: assert p.abi_argument_types() is not None assert p.abi_return_type() is not None else: assert p.abi_argument_types() is None assert p.abi_return_type() is None
def blackbox_pyteal_example5(): from graviton.blackbox import DryRunEncoder from pyteal import abi, Subroutine, TealType, Int, Mode from tests.blackbox import Blackbox @Blackbox([None]) @Subroutine(TealType.uint64) def cubed(n: abi.Uint64): return n.get()**Int(3) app_pytealer = PyTealDryRunExecutor(cubed, Mode.Application) lsig_pytealer = PyTealDryRunExecutor(cubed, Mode.Signature) inputs = [[i] for i in range(1, 11)] app_inspect = app_pytealer.dryrun_on_sequence(inputs) lsig_inspect = lsig_pytealer.dryrun_on_sequence(inputs) for index, inspect in enumerate(app_inspect): input_var = inputs[index][0] assert inspect.stack_top() == input_var**3, inspect.report( args=inputs[index], msg="stack_top() gave unexpected results from app") assert inspect.last_log() == DryRunEncoder.hex( input_var**3), inspect.report( args=inputs[index], msg="last_log() gave unexpected results from app") for index, inspect in enumerate(lsig_inspect): input_var = inputs[index][0] assert inspect.stack_top() == input_var**3, inspect.report( args=inputs[index], msg="stack_top() gave unexpected results from app")
def wrap_compile_and_save(subr, mode, version, assemble_constants, test_name, case_name): is_app = mode == pt.Mode.Application teal = PyTealDryRunExecutor(subr, mode).compile(version, assemble_constants) tealfile = f'{"app" if is_app else "lsig"}_{case_name}.teal' tealdir = GENERATED / test_name tealdir.mkdir(parents=True, exist_ok=True) tealpath = tealdir / tealfile with open(tealpath, "w") as f: f.write(teal) print(f"""Subroutine {case_name}@{mode} generated TEAL. saved to {tealpath}: ------- {teal} -------""") return teal, is_app, tealfile
def blackbox_pyteal_while_continue_test(): from tests.blackbox import Blackbox from pyteal import ( Continue, Int, Mode, Return, ScratchVar, Seq, Subroutine, TealType, While, ) @Blackbox(input_types=[TealType.uint64]) @Subroutine(TealType.uint64) def while_continue_accumulation(n): i = ScratchVar(TealType.uint64) return Seq( i.store(Int(0)), While(i.load() < n).Do( Seq( i.store(i.load() + Int(1)), Continue(), )), Return(i.load()), ) for x in range(30): args = [x] lsig_result = PyTealDryRunExecutor(while_continue_accumulation, Mode.Signature).dryrun(args) if x == 0: assert not lsig_result.passed() else: assert lsig_result.passed() assert lsig_result.stack_top() == x, lsig_result.report( args, "stack_top() gave unexpected results for lsig")
def test_verify(): @Blackbox(input_types=[]) @Subroutine(TealType.uint64) def verify(): return EcdsaVerify( EcdsaCurve.Secp256k1, Sha512_256(Bytes("testdata")), Bytes( "base16", "33602297203d2753372cea7794ffe1756a278cbc4907b15a0dd132c9fb82555e", ), Bytes( "base16", "20f112126cf3e2eac6e8d4f97a403d21bab07b8dbb77154511bb7b07c0173195", ), ( Bytes( "base16", "d6143a58c90c06b594e4414cb788659c2805e0056b1dfceea32c03f59efec517", ), Bytes( "base16", "00bd2400c479efe5ea556f37e1dc11ccb20f1e642dbfe00ca346fffeae508298", ), ), ) args = [] app_result = PyTealDryRunExecutor(verify, Mode.Application).dryrun( args, compiler_version=5) assert app_result.stack_top() == 1, app_result.report( args, "stack_top() is not equal to 1, indicating ecdsa verification failed.") @Blackbox(input_types=[]) @Subroutine(TealType.uint64) def verify_fail(): return EcdsaVerify( EcdsaCurve.Secp256k1, Sha512_256(Bytes("testdata")), Bytes( "base16", "13602297203d2753372cea7794ffe1756a278cbc4907b15a0dd132c9fb82555e", ), Bytes( "base16", "20f112126cf3e2eac6e8d4f97a403d21bab07b8dbb77154511bb7b07c0173195", ), ( Bytes( "base16", "d6143a58c90c06b594e4414cb788659c2805e0056b1dfceea32c03f59efec517", ), Bytes( "base16", "00bd2400c479efe5ea556f37e1dc11ccb20f1e642dbfe00ca346fffeae508298", ), ), ) args = [] app_result = PyTealDryRunExecutor(verify_fail, Mode.Application).dryrun( args, compiler_version=5) assert app_result.stack_top() == 0, app_result.report( args, "stack_top() is not equal to 0, indicating ecdsa verification succeeded when a failure was expected.", )
def blackbox_pyteal_example3(): # Example 3: declarative Test Driven Development approach through Invariant's from itertools import product import math import random from graviton.blackbox import ( DryRunEncoder, DryRunProperty as DRProp, ) from graviton.invariant import Invariant from pyteal import If, Int, Mod, Mode, Subroutine, TealType from tests.blackbox import Blackbox # avoid flaky tests just in case I was wrong about the stack height invariant... random.seed(42) # helper that will be used for scratch-slots invariant: def is_subdict(x, y): return all(k in y and x[k] == y[k] for k in x) predicates = { # the program's log should be the hex encoding of Python's math.gcd: DRProp.lastLog: lambda args: (DryRunEncoder.hex(math.gcd(*args)) if math.gcd(*args) else None), # the program's scratch should contain math.gcd() at slot 0: DRProp.finalScratch: lambda args, actual: is_subdict({0: math.gcd(*args)}, actual), # the top of the stack should be math.gcd(): DRProp.stackTop: lambda args: math.gcd(*args), # Making the rather weak assertion that the max stack height is between 2 and 3*log2(max(args)): DRProp.maxStackHeight: (lambda args, actual: 2 <= actual <= 3 * math.ceil( math.log2(max(args + (1, ))))), # the program PASS'es exactly for non-0 math.gcd (3 variants): DRProp.status: lambda args: "PASS" if math.gcd(*args) else "REJECT", DRProp.passed: lambda args: bool(math.gcd(*args)), DRProp.rejected: lambda args: not bool(math.gcd(*args)), # the program never errors: DRProp.errorMessage: None, } # Define a scenario 400 random pairs (x,y) as inputs: N = 20 inputs = list( product( tuple(random.randint(0, 1000) for _ in range(N)), tuple(random.randint(0, 1000) for _ in range(N)), )) # GCD via the Euclidean Algorithm (recursive version): @Blackbox(input_types=[TealType.uint64, TealType.uint64]) @Subroutine(TealType.uint64) def euclid(x, y): return (If(x < y).Then(euclid(y, x)).Else( If(y == Int(0)).Then(x).Else(euclid(y, Mod(x, y))))) # Execute on the input sequence to get a dry-run inspectors: inspectors = PyTealDryRunExecutor( euclid, Mode.Application).dryrun_on_sequence(inputs) # Assert that each invariant holds on the sequences of inputs and dry-runs: for property, predicate in predicates.items(): Invariant(predicate).validates(property, inputs, inspectors)
def pytealer(self) -> PyTealDryRunExecutor: roundtrip = self.roundtrip_factory() return PyTealDryRunExecutor(roundtrip, pt.Mode.Application)
def test_PyTealBlackboxExecutor_abi_return_type( mode: pt.Mode, fn: BlackboxWrapper, expected_does_produce_type: bool): if expected_does_produce_type: assert PyTealDryRunExecutor(fn, mode).abi_return_type() is not None else: assert PyTealDryRunExecutor(fn, mode).abi_return_type() is None
def test_PyTealBlackboxExecutor_abi_argument_types(mode: pt.Mode, fn: BlackboxWrapper, expected_arg_count: int): actual = PyTealDryRunExecutor(fn, mode).abi_argument_types() assert actual is not None assert len(actual) == expected_arg_count
def blackbox_pyteal_example4(): # Example 4: Using PyTealDryRunExecutor to debug an ABIReturnSubroutine with an app, logic sig and csv report from pathlib import Path import random from graviton.blackbox import DryRunInspector from pyteal import ( abi, ABIReturnSubroutine, Expr, For, Int, Mode, ScratchVar, Seq, TealType, ) from tests.blackbox import Blackbox, PyTealDryRunExecutor # Sum a dynamic uint64 array @Blackbox(input_types=[None]) @ABIReturnSubroutine def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr: i = ScratchVar(TealType.uint64) valueAtIndex = abi.Uint64() return Seq( output.set(0), For( i.store(Int(0)), i.load() < toSum.length(), i.store(i.load() + Int(1)), ).Do( Seq( toSum[i.load()].store_into(valueAtIndex), output.set(output.get() + valueAtIndex.get()), )), ) # instantiate PyTealDryRunExecutor objects for the app and lsig: app_pytealer = PyTealDryRunExecutor(abi_sum, Mode.Application) lsig_pytealer = PyTealDryRunExecutor(abi_sum, Mode.Signature) # generate reports with the same random inputs (fix the randomness with a seed): random.seed(42) N = 50 # the number of dry runs for each experiment choices = range(10_000) inputs = [] for n in range(N): inputs.append(tuple([random.sample(choices, n)])) app_inspectors = app_pytealer.dryrun_on_sequence(inputs) lsig_inspectors = lsig_pytealer.dryrun_on_sequence(inputs) for i in range(N): args = inputs[i] app_inspector = app_inspectors[i] lsig_inspector = lsig_inspectors[i] def message(insp): return insp.report(args, f"failed for {args}", row=i) # the app should pass exactly when it's cost was within the 700 budget: assert app_inspector.passed() == (app_inspector.cost() <= 700), message(app_inspector) # the lsig always passes (never goes over budget): assert lsig_inspector.passed(), message(lsig_inspector) expected = sum(args[0]) actual4app = app_inspector.last_log() assert expected == actual4app, message(app_inspector) if i > 0: assert expected in app_inspector.final_scratch().values(), message( app_inspector) assert expected in lsig_inspector.final_scratch().values( ), message(lsig_inspector) def report(kind): assert kind in ("app", "lsig") insps = app_inspectors if kind == "app" else lsig_inspectors csv_report = DryRunInspector.csv_report(inputs, insps) with open(Path.cwd() / f"abi_sum_{kind}.csv", "w") as f: f.write(csv_report) report("app") report("lsig")