Skip to content

Commit

Permalink
Feature Gates without a Config File (#687)
Browse files Browse the repository at this point in the history
  • Loading branch information
tzaffi committed Mar 14, 2023
1 parent d1961a6 commit 6708938
Show file tree
Hide file tree
Showing 45 changed files with 749 additions and 546 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

## Changed

* Enable source maps using the new `FeatureGate` class. See `examples/applications/sourcemap.py` for a usage example. ([#687](https://github.com/algorand/pyteal/pull/687))

# v0.23.0

## Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ bundle-docs: bundle-docs-clean
check-generate-init:
python -m scripts.generate_init --check

ALLPY = docs examples pyteal scripts tests *.py
ALLPY = docs examples feature_gates pyteal scripts tests *.py
black:
black --check $(ALLPY)

Expand Down
61 changes: 61 additions & 0 deletions examples/application/sourcemap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
This example shows how to use the source map feature to for a PyTeal program.
To enable the source mapping (at the time of this writing 3/14/2023)
one must import `FeatureGate` from `feature_gates` and call `FeatureGate.set_sourcemap_enabled(True)`.
This import must occur before any object from PyTeal is imported, as PyTeal will
default to its own sourcemap configuration if not set beforehand, and PyTeal imports have
the side effect of importing expressions that the sourcemapper needs to be aware of.
"""

from feature_gates import FeatureGates

FeatureGates.set_sourcemap_enabled(True)

# INTENTIONALLY importing pyteal and objects _AFTER_ enabling the sourcemap feature
import pyteal as pt # noqa: E402
from pyteal import Compilation, Mode # noqa: E402


def program():
@pt.Subroutine(pt.TealType.uint64)
def is_even(i):
return (
pt.If(i == pt.Int(0))
.Then(pt.Int(1))
.ElseIf(i == pt.Int(1))
.Then(pt.Int(0))
.Else(is_even(i - pt.Int(2)))
)

n = pt.Btoi(arg0 := pt.Txn.application_args[0])

return pt.Seq(
pt.Log(pt.Bytes("n:")),
pt.Log(arg0),
pt.Log(pt.Bytes("is_even(n):")),
pt.Log(pt.Itob(is_even(n))),
pt.Int(1),
)


if __name__ == "__main__":
# This assumes running as follows:
# cd examples/application
# python sourcemap.py
approval_program = program()

results = Compilation(approval_program, mode=Mode.Application, version=8).compile(
with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True
)

teal = "teal/sourcemap.teal"
annotated = "teal/sourcemap_annotated.teal"

with open(teal, "w") as f:
f.write(results.teal)

with open(annotated, "w") as f:
f.write(results.sourcemap.annotated_teal)

print(f"SUCCESS!!! Please check out {teal} and {annotated}")
38 changes: 38 additions & 0 deletions examples/application/teal/sourcemap.teal
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma version 8
byte "n:"
log
txna ApplicationArgs 0
log
byte "is_even(n):"
log
txna ApplicationArgs 0
btoi
callsub iseven_0
itob
log
int 1
return

// is_even
iseven_0:
proto 1 1
frame_dig -1
int 0
==
bnz iseven_0_l4
frame_dig -1
int 1
==
bnz iseven_0_l3
frame_dig -1
int 2
-
callsub iseven_0
b iseven_0_l5
iseven_0_l3:
int 0
b iseven_0_l5
iseven_0_l4:
int 1
iseven_0_l5:
retsub
39 changes: 39 additions & 0 deletions examples/application/teal/sourcemap_annotated.teal
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// GENERATED TEAL // PYTEAL PATH LINE PYTEAL
#pragma version 8 // sourcemap.py 48 Compilation(approval_program, mode=Mode.Application, version=8).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True)
byte "n:" // 34 pt.Bytes('n:')
log // pt.Log(pt.Bytes('n:'))
txna ApplicationArgs 0 // 31 pt.Txn.application_args[0]
log // 35 pt.Log(arg0)
byte "is_even(n):" // 36 pt.Bytes('is_even(n):')
log // pt.Log(pt.Bytes('is_even(n):'))
txna ApplicationArgs 0 // 31 pt.Txn.application_args[0]
btoi // pt.Btoi((arg0 := pt.Txn.application_args[0]))
callsub iseven_0 // 37 is_even(n)
itob // pt.Itob(is_even(n))
log // pt.Log(pt.Itob(is_even(n)))
int 1 // 38 pt.Int(1)
return // 48 Compilation(approval_program, mode=Mode.Application, version=8).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True)
// 22 def is_even(i):
// is_even //
iseven_0: //
proto 1 1 //
frame_dig -1 // 48 Compilation(approval_program, mode=Mode.Application, version=8).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True)
int 0 // 24 pt.Int(0)
== // i == pt.Int(0)
bnz iseven_0_l4 // pt.If(i == pt.Int(0))
frame_dig -1 // 48 Compilation(approval_program, mode=Mode.Application, version=8).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True)
int 1 // 26 pt.Int(1)
== // i == pt.Int(1)
bnz iseven_0_l3 //
frame_dig -1 // 48 Compilation(approval_program, mode=Mode.Application, version=8).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True)
int 2 // 28 pt.Int(2)
- // i - pt.Int(2)
callsub iseven_0 // is_even(i - pt.Int(2))
b iseven_0_l5 // 26 pt.If(i == pt.Int(0)).Then(pt.Int(1)).ElseIf(i == pt.Int(1))
iseven_0_l3: // i == pt.Int(1)
int 0 // 27 pt.Int(0)
b iseven_0_l5 // 48 Compilation(approval_program, mode=Mode.Application, version=8).compile(with_sourcemap=True, annotate_teal=True, annotate_teal_headers=True)
iseven_0_l4: // 24 pt.If(i == pt.Int(0))
int 1 // 25 pt.Int(1)
iseven_0_l5: // 26 pt.If(i == pt.Int(0)).Then(pt.Int(1)).ElseIf(i == pt.Int(1))
retsub // 22 def is_even(i):
1 change: 1 addition & 0 deletions examples/application/vote_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
algod_address = "http://localhost:4001"
algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"


# helper function to compile program source
def compile_program(client, source_code):
compile_response = client.compile(source_code)
Expand Down
1 change: 0 additions & 1 deletion examples/signature/atomic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def htlc(
tmpl_hash_fn=Sha256,
tmpl_timeout=timeout,
):

fee_cond = Txn.fee() < Int(tmpl_fee)
safety_cond = And(
Txn.type_enum() == TxnType.Payment,
Expand Down
1 change: 0 additions & 1 deletion examples/signature/periodic_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ def periodic_payment(
tmpl_rcv=tmpl_rcv,
tmpl_timeout=tmpl_timeout,
):

periodic_pay_core = And(
Txn.type_enum() == TxnType.Payment,
Txn.fee() < tmpl_fee,
Expand Down
1 change: 0 additions & 1 deletion examples/signature/split.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ def split(
tmpl_min_pay=tmpl_min_pay,
tmpl_timeout=tmpl_timeout,
):

split_core = And(
Txn.type_enum() == TxnType.Payment,
Txn.fee() < tmpl_fee,
Expand Down
65 changes: 65 additions & 0 deletions feature_gates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from dataclasses import dataclass
from typing import Final


@dataclass
class _FeatureGatesConfig:
"""
Configuration for feature gates.
We follow the following convention:
feature_property: True/False
"""

sourcemap_enabled: bool
sourcemap_debug: bool


class FeatureGates:
"""
Feature gates work as follows:
1. Add a new feature gate to the _FeatureGatesConfig dataclass
2. Set the default value for the feature gate in the _gates field below
Automagically, a new setter and getter will be available for the feature gate.
For example, given feature `foo_on` by adding _FeatureGatesConfig.foo_on,
the following methods will be available:
* FeatureGates.foo_on() -> bool
* FeatureGates.set_foo_on(gate: bool)
"""

# default values for feature gates:
_gates: _FeatureGatesConfig = _FeatureGatesConfig(
sourcemap_enabled=False,
sourcemap_debug=False,
)
_features: Final[set[str]] = set(vars(_gates).keys())

@classmethod
def _make_feature(cls, feature: str):
setattr(cls, feature, classmethod(lambda cls: cls.get(feature)))
setattr(
cls, f"set_{feature}", classmethod(lambda cls, gate: cls.set(feature, gate))
)

@classmethod
def get(cls, feature: str) -> bool | None:
if feature not in cls._features:
raise ValueError(
f"Unknown {feature=} (available features: {cls._features}))"
)

return getattr(cls._gates, feature)

@classmethod
def set(cls, feature: str, gate: bool) -> None:
if feature not in cls._features:
raise ValueError(
f"Cannot set unknown {feature=} (available features: {cls._features}))"
)
setattr(cls._gates, feature, gate)


for feature in FeatureGates._features:
FeatureGates._make_feature(feature)
5 changes: 0 additions & 5 deletions pyteal.ini

This file was deleted.

72 changes: 36 additions & 36 deletions pyteal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,69 @@
from pyteal.ast import *
from pyteal.ast import __all__ as ast_all
from pyteal.pragma import pragma
from pyteal.ir import *
from pyteal.ir import __all__ as ir_all
from pyteal.compiler import (
MAX_TEAL_VERSION,
MIN_TEAL_VERSION,
DEFAULT_PROGRAM_VERSION,
DEFAULT_TEAL_VERSION,
MAX_PROGRAM_VERSION,
MAX_TEAL_VERSION,
MIN_PROGRAM_VERSION,
DEFAULT_PROGRAM_VERSION,
CompileOptions,
MIN_TEAL_VERSION,
Compilation,
compileTeal,
CompileOptions,
OptimizeOptions,
PyTealSourceMap,
compileTeal,
)
from pyteal.config import (
MAX_GROUP_SIZE,
METHOD_ARG_NUM_CUTOFF,
NUM_SLOTS,
RETURN_HASH_PREFIX,
)
from pyteal.types import TealType
from pyteal.errors import (
AlgodClientError,
SourceMapDisabledError,
TealInternalError,
TealTypeError,
TealSeqError,
TealInputError,
TealCompileError,
TealInputError,
TealInternalError,
TealPragmaError,
TealSeqError,
TealTypeError,
)
from pyteal.config import (
MAX_GROUP_SIZE,
NUM_SLOTS,
RETURN_HASH_PREFIX,
METHOD_ARG_NUM_CUTOFF,
)
from pyteal.ir import *
from pyteal.ir import __all__ as ir_all
from pyteal.pragma import pragma
from pyteal.types import TealType

# begin __all__
__all__ = (
ast_all
+ ir_all
+ [
"MAX_TEAL_VERSION",
"MIN_TEAL_VERSION",
"AlgodClientError",
"Compilation",
"CompileOptions",
"compileTeal",
"DEFAULT_PROGRAM_VERSION",
"DEFAULT_TEAL_VERSION",
"MAX_GROUP_SIZE",
"MAX_PROGRAM_VERSION",
"MAX_TEAL_VERSION",
"METHOD_ARG_NUM_CUTOFF",
"MIN_PROGRAM_VERSION",
"DEFAULT_PROGRAM_VERSION",
"CompileOptions",
"pragma",
"Compilation",
"compileTeal",
"MIN_TEAL_VERSION",
"NUM_SLOTS",
"OptimizeOptions",
"pragma",
"PyTealSourceMap",
"TealType",
"AlgodClientError",
"RETURN_HASH_PREFIX",
"SourceMapDisabledError",
"TealInternalError",
"TealTypeError",
"TealSeqError",
"TealInputError",
"TealCompileError",
"TealInputError",
"TealInternalError",
"TealPragmaError",
"MAX_GROUP_SIZE",
"NUM_SLOTS",
"RETURN_HASH_PREFIX",
"METHOD_ARG_NUM_CUTOFF",
"TealSeqError",
"TealType",
"TealTypeError",
]
)
# end __all__

0 comments on commit 6708938

Please sign in to comment.