def test_ecdsa_decompress(curve: pt.EcdsaCurve): compressed_pubkey = pt.Bytes("XY") pubkey = pt.EcdsaDecompress(curve, compressed_pubkey) assert pubkey.type_of() == pt.TealType.none expected = pt.TealSimpleBlock([ pt.TealOp(compressed_pubkey, pt.Op.byte, '"XY"'), pt.TealOp(pubkey, pt.Op.ecdsa_pk_decompress, curve.arg_name), pt.TealOp(pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1]), pt.TealOp(pubkey.output_slots[0].store(), pt.Op.store, pubkey.output_slots[0]), ]) actual, _ = pubkey.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected # compile without errors this is necessary so assembly is also tested pt.compileTeal(pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version) with pytest.raises(pt.TealInputError): pt.compileTeal( pt.Seq(pubkey, pt.Approve()), pt.Mode.Application, version=curve.min_version - 1, )
def test_ecdsa_verify_basic(curve: pt.EcdsaCurve): args = [pt.Bytes("data"), pt.Bytes("sigA"), pt.Bytes("sigB")] pubkey = (pt.Bytes("X"), pt.Bytes("Y")) expr = pt.EcdsaVerify(curve, args[0], args[1], args[2], pubkey) assert expr.type_of() == pt.TealType.uint64 expected = pt.TealSimpleBlock([ pt.TealOp(args[0], pt.Op.byte, '"data"'), pt.TealOp(args[1], pt.Op.byte, '"sigA"'), pt.TealOp(args[2], pt.Op.byte, '"sigB"'), pt.TealOp(pubkey[0], pt.Op.byte, '"X"'), pt.TealOp(pubkey[1], pt.Op.byte, '"Y"'), pt.TealOp(expr, pt.Op.ecdsa_verify, curve.arg_name), ]) actual, _ = expr.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) assert actual == expected # compile without errors this is necessary so assembly is also tested pt.compileTeal( pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=curve.min_version, ) with pytest.raises(pt.TealInputError): pt.compileTeal( pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=curve.min_version - 1, )
def test_wrap_handler_bare_call(): BARE_CALL_CASES = [ dummy_doing_nothing, safe_clear_state_delete, pt.Approve(), pt.Log(pt.Bytes("message")), ] for bare_call in BARE_CALL_CASES: wrapped: pt.Expr = ASTBuilder.wrap_handler(False, bare_call) expected: pt.Expr match bare_call: case pt.Expr(): if bare_call.has_return(): expected = bare_call else: expected = pt.Seq(bare_call, pt.Approve()) case pt.SubroutineFnWrapper() | pt.ABIReturnSubroutine(): expected = pt.Seq(bare_call(), pt.Approve()) case _: raise pt.TealInputError("how you got here?") wrapped_assemble = assemble_helper(wrapped) wrapped_helper = assemble_helper(expected) with pt.TealComponent.Context.ignoreExprEquality(): assert wrapped_assemble == wrapped_helper ERROR_CASES = [ ( pt.Int(1), f"bare appcall handler should be TealType.none not {pt.TealType.uint64}.", ), ( returning_u64, f"subroutine call should be returning TealType.none not {pt.TealType.uint64}.", ), ( mult_over_u64_and_log, "subroutine call should take 0 arg for bare-app call. this subroutine takes 2.", ), ( eine_constant, f"abi-returning subroutine call should be returning void not {pt.abi.Uint64TypeSpec()}.", ), ( take_abi_and_log, "abi-returning subroutine call should take 0 arg for bare-app call. this abi-returning subroutine takes 1.", ), ( 1, "bare appcall can only accept: none type Expr, or Subroutine/ABIReturnSubroutine with none return and no arg", ), ] for error_case, error_msg in ERROR_CASES: with pytest.raises(pt.TealInputError) as bug: ASTBuilder.wrap_handler(False, error_case) assert error_msg in str(bug)
def test_build_program_clear_state_valid_config(): action = pt.If(pt.Txn.fee() == pt.Int(4)).Then(pt.Approve()).Else(pt.Reject()) config = pt.CallConfig.CALL router_with_bare_call = pt.Router( "test", pt.BareCallActions( clear_state=pt.OnCompleteAction(action=action, call_config=config) ), ) _, actual_clear_state_with_bare_call, _ = router_with_bare_call.build_program() expected_clear_state_with_bare_call = assemble_helper( pt.Cond([pt.Txn.application_args.length() == pt.Int(0), action]) ) with pt.TealComponent.Context.ignoreExprEquality(): assert ( assemble_helper(actual_clear_state_with_bare_call) == expected_clear_state_with_bare_call ) router_with_method = pt.Router("test") @pt.ABIReturnSubroutine def clear_state_method(): return action router_with_method.add_method_handler( clear_state_method, method_config=pt.MethodConfig(clear_state=config) ) _, actual_clear_state_with_method, _ = router_with_method.build_program() expected_clear_state_with_method = assemble_helper( pt.Cond( [ pt.Txn.application_args[0] == pt.MethodSignature("clear_state_method()void"), pt.Seq(clear_state_method(), pt.Approve()), ] ) ) with pt.TealComponent.Context.ignoreExprEquality(): assert ( assemble_helper(actual_clear_state_with_method) == expected_clear_state_with_method )
def test_wrap_handler_method_txn_types(): wrapped: pt.Expr = ASTBuilder.wrap_handler(True, multiple_txn) actual: pt.TealBlock = assemble_helper(wrapped) args: list[pt.abi.Transaction] = [ pt.abi.ApplicationCallTransaction(), pt.abi.AssetTransferTransaction(), pt.abi.PaymentTransaction(), pt.abi.Transaction(), ] output_temp = pt.abi.Uint64() expected_ast = pt.Seq( args[0]._set_index(pt.Txn.group_index() - pt.Int(4)), pt.Assert(args[0].get().type_enum() == pt.TxnType.ApplicationCall), args[1]._set_index(pt.Txn.group_index() - pt.Int(3)), pt.Assert(args[1].get().type_enum() == pt.TxnType.AssetTransfer), args[2]._set_index(pt.Txn.group_index() - pt.Int(2)), pt.Assert(args[2].get().type_enum() == pt.TxnType.Payment), args[3]._set_index(pt.Txn.group_index() - pt.Int(1)), multiple_txn(*args).store_into(output_temp), pt.abi.MethodReturn(output_temp), pt.Approve(), ) expected = assemble_helper(expected_ast) with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected assert pt.TealBlock.MatchScratchSlotReferences( pt.TealBlock.GetReferencedScratchSlots(actual), pt.TealBlock.GetReferencedScratchSlots(expected), )
def test_build_program_clear_state_invalid_config(): for config in (pt.CallConfig.CREATE, pt.CallConfig.ALL): bareCalls = pt.BareCallActions( clear_state=pt.OnCompleteAction(action=pt.Approve(), call_config=config) ) with pytest.raises( pt.TealInputError, match=r"Only CallConfig.CALL or CallConfig.NEVER are valid for a clear state CallConfig, since clear state can never be invoked during creation$", ): pt.Router("test", bareCalls) router = pt.Router("test") @pt.ABIReturnSubroutine def clear_state_method(): return pt.Approve() with pytest.raises( pt.TealInputError, match=r"Only CallConfig.CALL or CallConfig.NEVER are valid for a clear state CallConfig, since clear state can never be invoked during creation$", ): router.add_method_handler( clear_state_method, method_config=pt.MethodConfig(clear_state=config), )
def test_ecdsa_verify_recovered_pk(): curve = pt.EcdsaCurve.Secp256k1 args = [pt.Bytes("data"), pt.Int(1), pt.Bytes("sigA"), pt.Bytes("sigB")] pubkey = pt.EcdsaRecover(curve, args[0], args[1], args[2], args[3]) expr = pt.EcdsaVerify(curve, args[0], args[2], args[3], pubkey) assert expr.type_of() == pt.TealType.uint64 expected = pt.TealSimpleBlock([ pt.TealOp(args[0], pt.Op.byte, '"data"'), pt.TealOp(args[1], pt.Op.int, 1), pt.TealOp(args[2], pt.Op.byte, '"sigA"'), pt.TealOp(args[3], pt.Op.byte, '"sigB"'), pt.TealOp(pubkey, pt.Op.ecdsa_pk_recover, curve.arg_name), pt.TealOp(pubkey.output_slots[1].store(), pt.Op.store, pubkey.output_slots[1]), pt.TealOp(pubkey.output_slots[0].store(), pt.Op.store, pubkey.output_slots[0]), pt.TealOp(args[0], pt.Op.byte, '"data"'), pt.TealOp(args[1], pt.Op.byte, '"sigA"'), pt.TealOp(args[2], pt.Op.byte, '"sigB"'), pt.TealOp(pubkey.output_slots[0].load(), pt.Op.load, pubkey.output_slots[0]), pt.TealOp(pubkey.output_slots[1].load(), pt.Op.load, pubkey.output_slots[1]), pt.TealOp(expr, pt.Op.ecdsa_verify, curve.arg_name), ]) actual, _ = expr.__teal__(curve_options_map[curve]) actual.addIncoming() actual = pt.TealBlock.NormalizeBlocks(actual) with pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected # compile without errors this is necessary so assembly is also tested pt.compileTeal( pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=curve.min_version, ) with pytest.raises(pt.TealInputError): pt.compileTeal( pt.Seq(pt.Pop(expr), pt.Approve()), pt.Mode.Application, version=curve.min_version - 1, )
def test_pragma_expr(compiler_version, should_error): program = pt.Pragma(pt.Approve(), compiler_version=compiler_version) if should_error: with pytest.raises(pt.TealPragmaError): pt.compileTeal(program, mode=pt.Mode.Application, version=6) else: pt.compileTeal(program, mode=pt.Mode.Application, version=6)
def test_optimize_subroutine_with_global_var(): global_var = pt.ScratchVar(pt.TealType.uint64) @pt.Subroutine(pt.TealType.uint64) def add(a1: pt.Expr) -> pt.Expr: return pt.Seq(global_var.store(pt.Int(2)), global_var.load() + a1) program = pt.Seq([ pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( pt.Pop(add(pt.Int(1)))), global_var.store(pt.Int(5)), pt.Approve(), ]) optimize_options = OptimizeOptions() # unoptimized expected = """#pragma version 4 txn Sender global CreatorAddress == bz main_l2 int 1 callsub add_0 pop main_l2: int 5 store 0 int 1 return // add add_0: store 1 int 2 store 0 load 0 load 1 + retsub """.strip() actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected # optimization should not apply to global vars optimize_options = OptimizeOptions(scratch_slots=True) actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected
def test_optimize_subroutine_with_reserved_local_var(): local_var = pt.ScratchVar(pt.TealType.uint64, 0) @pt.Subroutine(pt.TealType.uint64) def add(a1: pt.Expr) -> pt.Expr: return pt.Seq(local_var.store(pt.Int(2)), local_var.load() + a1) program = pt.Seq([ pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( pt.Pop(add(pt.Int(1)))), pt.Approve(), ]) optimize_options = OptimizeOptions() # unoptimized expected = """#pragma version 4 txn Sender global CreatorAddress == bz main_l2 int 1 callsub add_0 pop main_l2: int 1 return // add add_0: store 1 int 2 store 0 load 0 load 1 + retsub """.strip() actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected # The optimization must skip over the reserved slot id so the expected result # hasn't changed. optimize_options = OptimizeOptions(scratch_slots=True) actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected
def test_optimize_multi_value(): # note: this is incorrect usage of the app_global_get_ex opcode program = pt.Seq( pt.MultiValue( pt.Op.app_global_get_ex, [pt.TealType.uint64, pt.TealType.uint64], immediate_args=[], args=[pt.Int(0), pt.Int(1)], ).outputReducer(lambda value, hasValue: pt.Pop(value + hasValue)), pt.Approve(), ) optimize_options = OptimizeOptions() # unoptimized expected = """#pragma version 4 int 0 int 1 app_global_get_ex store 1 store 0 load 0 load 1 + pop int 1 return""".strip() actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected # optimized expected = """#pragma version 4 int 0 int 1 app_global_get_ex + pop int 1 return""".strip() optimize_options = OptimizeOptions(scratch_slots=True) actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected
def test_wrap_handler_method_call_many_args(): wrapped: pt.Expr = ASTBuilder.wrap_handler(True, many_args) actual: pt.TealBlock = assemble_helper(wrapped) args = [pt.abi.Uint64() for _ in range(20)] last_arg = pt.abi.TupleTypeSpec( *[pt.abi.Uint64TypeSpec() for _ in range(6)] ).new_instance() output_temp = pt.abi.Uint64() expected_ast = pt.Seq( args[0].decode(pt.Txn.application_args[1]), args[1].decode(pt.Txn.application_args[2]), args[2].decode(pt.Txn.application_args[3]), args[3].decode(pt.Txn.application_args[4]), args[4].decode(pt.Txn.application_args[5]), args[5].decode(pt.Txn.application_args[6]), args[6].decode(pt.Txn.application_args[7]), args[7].decode(pt.Txn.application_args[8]), args[8].decode(pt.Txn.application_args[9]), args[9].decode(pt.Txn.application_args[10]), args[10].decode(pt.Txn.application_args[11]), args[11].decode(pt.Txn.application_args[12]), args[12].decode(pt.Txn.application_args[13]), args[13].decode(pt.Txn.application_args[14]), last_arg.decode(pt.Txn.application_args[15]), last_arg[0].store_into(args[14]), last_arg[1].store_into(args[15]), last_arg[2].store_into(args[16]), last_arg[3].store_into(args[17]), last_arg[4].store_into(args[18]), last_arg[5].store_into(args[19]), many_args(*args).store_into(output_temp), pt.abi.MethodReturn(output_temp), pt.Approve(), ) expected = assemble_helper(expected_ast) with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected assert pt.TealBlock.MatchScratchSlotReferences( pt.TealBlock.GetReferencedScratchSlots(actual), pt.TealBlock.GetReferencedScratchSlots(expected), )
def test_build_program_clear_state_empty(): router = pt.Router( "test", pt.BareCallActions(no_op=pt.OnCompleteAction.always(pt.Approve())) ) approval, clear_state, contract = router.build_program() expected_empty_program = pt.TealSimpleBlock( [ pt.TealOp(None, pt.Op.int, 0), pt.TealOp(None, pt.Op.return_), ] ) with pt.TealComponent.Context.ignoreExprEquality(): assert assemble_helper(approval) != expected_empty_program assert assemble_helper(clear_state) == expected_empty_program expected_contract = sdk_abi.Contract("test", []) assert contract == expected_contract
def test_optimize_dynamic_var(): myvar = pt.DynamicScratchVar() regvar = pt.ScratchVar() program = pt.Seq( regvar.store(pt.Int(1)), myvar.set_index(regvar), regvar.store(pt.Int(2)), pt.Pop(regvar.load()), pt.Approve(), ) optimize_options = OptimizeOptions() # unoptimized expected = """#pragma version 4 int 1 store 1 int 1 store 0 int 2 store 1 load 1 pop int 1 return""".strip() actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected # optimization should not change the code because the candidate slot # is used by the dynamic slot variable. optimize_options = OptimizeOptions(scratch_slots=True) actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected
def clear_state_method(): return pt.Approve()
def test_wrap_handler_method_call(): with pytest.raises(pt.TealInputError) as bug: ASTBuilder.wrap_handler(True, not_registrable) assert "method call ABIReturnSubroutine is not routable" in str(bug) with pytest.raises(pt.TealInputError) as bug: ASTBuilder.wrap_handler(True, safe_clear_state_delete) assert "method call should be only registering ABIReturnSubroutine" in str(bug) ONLY_ABI_SUBROUTINE_CASES = list( filter(lambda x: isinstance(x, pt.ABIReturnSubroutine), GOOD_SUBROUTINE_CASES) ) for abi_subroutine in ONLY_ABI_SUBROUTINE_CASES: wrapped: pt.Expr = ASTBuilder.wrap_handler(True, abi_subroutine) actual: pt.TealBlock = assemble_helper(wrapped) args: list[pt.abi.BaseType] = [ spec.new_instance() for spec in typing.cast( list[pt.abi.TypeSpec], abi_subroutine.subroutine.expected_arg_types ) ] app_args = [ arg for arg in args if arg.type_spec() not in pt.abi.TransactionTypeSpecs ] app_arg_cnt = len(app_args) txn_args: list[pt.abi.Transaction] = [ arg for arg in args if arg.type_spec() in pt.abi.TransactionTypeSpecs ] loading: list[pt.Expr] = [] if app_arg_cnt > pt.METHOD_ARG_NUM_CUTOFF: sdk_last_arg = pt.abi.TupleTypeSpec( *[arg.type_spec() for arg in app_args[pt.METHOD_ARG_NUM_CUTOFF - 1 :]] ).new_instance() loading = [ arg.decode(pt.Txn.application_args[index + 1]) for index, arg in enumerate(app_args[: pt.METHOD_ARG_NUM_CUTOFF - 1]) ] loading.append( sdk_last_arg.decode(pt.Txn.application_args[pt.METHOD_ARG_NUM_CUTOFF]) ) else: loading = [ arg.decode(pt.Txn.application_args[index + 1]) for index, arg in enumerate(app_args) ] if len(txn_args) > 0: for idx, txn_arg in enumerate(txn_args): loading.append( txn_arg._set_index( pt.Txn.group_index() - pt.Int(len(txn_args) - idx) ) ) if str(txn_arg.type_spec()) != "txn": loading.append( pt.Assert( txn_arg.get().type_enum() == txn_arg.type_spec().txn_type_enum() ) ) if app_arg_cnt > pt.METHOD_ARG_NUM_CUTOFF: loading.extend( [ sdk_last_arg[idx].store_into(val) for idx, val in enumerate(app_args[pt.METHOD_ARG_NUM_CUTOFF - 1 :]) ] ) evaluate: pt.Expr if abi_subroutine.type_of() != "void": output_temp = abi_subroutine.output_kwarg_info.abi_type.new_instance() evaluate = pt.Seq( abi_subroutine(*args).store_into(output_temp), pt.abi.MethodReturn(output_temp), ) else: evaluate = abi_subroutine(*args) expected = assemble_helper(pt.Seq(*loading, evaluate, pt.Approve())) with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality(): assert actual == expected assert pt.TealBlock.MatchScratchSlotReferences( pt.TealBlock.GetReferencedScratchSlots(actual), pt.TealBlock.GetReferencedScratchSlots(expected), )
def test_optimize_subroutine(): @pt.Subroutine(pt.TealType.uint64) def add(a1: pt.Expr, a2: pt.Expr) -> pt.Expr: return a1 + a2 program = pt.Seq([ pt.If(pt.Txn.sender() == pt.Global.creator_address()).Then( pt.Pop(add(pt.Int(1), pt.Int(2)))), pt.Approve(), ]) optimize_options = OptimizeOptions() # unoptimized expected = """#pragma version 4 txn Sender global CreatorAddress == bz main_l2 int 1 int 2 callsub add_0 pop main_l2: int 1 return // add add_0: store 1 store 0 load 0 load 1 + retsub """.strip() actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected # optimized expected = """#pragma version 4 txn Sender global CreatorAddress == bz main_l2 int 1 int 2 callsub add_0 pop main_l2: int 1 return // add add_0: + retsub """.strip() optimize_options = OptimizeOptions(scratch_slots=True) actual = pt.compileTeal(program, version=4, mode=pt.Mode.Application, optimize=optimize_options) assert actual == expected
def safe_clear_state_delete(): return ( pt.If(pt.Txn.sender() == pt.Global.creator_address()) .Then(pt.Approve()) .Else(pt.Reject()) )
def test_pragma_expr_invalid_compiler_version(compiler_version): with pytest.raises(ValueError): pt.Pragma(pt.Approve(), compiler_version=compiler_version)