diff --git a/examples/rich_types.tl b/examples/rich_types.tl new file mode 100644 index 0000000..24f4940 --- /dev/null +++ b/examples/rich_types.tl @@ -0,0 +1,9 @@ +#pragma version 8 + +const int I = 123 +const bytes B = "abc" +const bigint Bi = 1231231231231231231231231231231231 +const bigint One = 1 +assert((Bi b/ Bi) b== One) + +exit(1) \ No newline at end of file diff --git a/tealish/base.py b/tealish/base.py index a1e43ba..029db55 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -1,6 +1,13 @@ from typing import cast, Any, Dict, List, Optional, Tuple, TYPE_CHECKING from tealish.errors import CompileError -from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType +from .tealish_builtins import ( + AVMType, + ConstValue, + ScratchRecord, + VarType, + TealishType, + stack_type, +) from .langspec import get_active_langspec, Op from .scope import Scope @@ -15,17 +22,19 @@ def check_arg_types(name: str, incoming_args: List["Node"]) -> None: op = lang_spec.lookup_op(name) expected_args = op.arg_types - # TODO: for i, incoming_arg in enumerate(incoming_args): - if incoming_arg.type == AVMType.any: # type: ignore + tealish_type = incoming_arg.tealish_type() + avm_type = stack_type(tealish_type) + + if avm_type == AVMType.any: continue if expected_args[i] == AVMType.any: continue - if incoming_arg.type == expected_args[i]: # type: ignore + if tealish_type == expected_args[i]: continue raise Exception( - f"Incorrect type {incoming_arg.type} " # type: ignore + f"Incorrect type {tealish_type} " # type: ignore + f"for arg {i} of {name}. Expected {expected_args[i]}" ) @@ -142,7 +151,7 @@ def check_arg_types(self, name: str, args: List["Node"]) -> None: except Exception as e: raise CompileError(str(e), node=self) # type: ignore - def get_field_type(self, namespace: str, name: str) -> AVMType: + def get_field_type(self, namespace: str, name: str) -> TealishType: return lang_spec.get_field_type(namespace, name) def lookup_op(self, name: str) -> Op: @@ -154,12 +163,20 @@ def lookup_func(self, name: str) -> "Func": def lookup_var(self, name: str) -> Any: return self.get_scope().lookup_var(name) - def lookup_const(self, name: str) -> Tuple["AVMType", ConstValue]: + def lookup_const(self, name: str) -> Tuple["TealishType", ConstValue]: return self.get_scope().lookup_const(name) - def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: + def lookup_avm_constant(self, name: str) -> Tuple["TealishType", Any]: return lang_spec.lookup_avm_constant(name) + def tealish_type(self) -> TealishType: + if hasattr(self, "type"): + return cast(TealishType, getattr(self, "type")) + return TealishType.none + + def stack_type(self) -> AVMType: + return stack_type(self.tealish_type()) + # TODO: these attributes are only available on Node and other children types # we should either define them here or something else? @property diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 42db892..2c060e0 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -2,7 +2,13 @@ from .base import BaseNode from .errors import CompileError -from .tealish_builtins import AVMType, get_struct, ObjectType +from .tealish_builtins import ( + AVMType, + get_struct, + ObjectType, + TealishType, + stack_type, +) from .langspec import Op, type_lookup @@ -14,7 +20,7 @@ class Integer(BaseNode): def __init__(self, value: str, parent: Optional[BaseNode] = None) -> None: self.value = int(value) - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -27,7 +33,7 @@ def _tealish(self) -> str: class Bytes(BaseNode): def __init__(self, value: str, parent: Optional[BaseNode] = None) -> None: self.value = value - self.type = AVMType.bytes + self.type = TealishType.bytes self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -49,8 +55,8 @@ def process(self) -> None: raise CompileError(e.args[0], node=self) # is it a struct or box? if type(self.type) == tuple: - if self.type[0] == ObjectType.struct: - self.type = AVMType.bytes + if self.type[0] == ObjectType.scratch: + self.type = TealishType.bytes elif self.type[0] == ObjectType.box: raise CompileError("Invalid use of a Box reference", node=self) @@ -64,8 +70,8 @@ def _tealish(self) -> str: class Constant(BaseNode): def __init__(self, name: str, parent: Optional[BaseNode] = None) -> None: self.name = name - self.type: AVMType = AVMType.none self.parent = parent + self.type: TealishType def process(self) -> None: type, value = None, None @@ -80,16 +86,16 @@ def process(self) -> None: raise CompileError( f'Constant "{self.name}" not declared in scope', node=self ) - if type not in (AVMType.int, AVMType.bytes): + if stack_type(type) not in (AVMType.int, AVMType.bytes): raise CompileError(f"Unexpected const type {type}", node=self) self.type = type self.value = value def write_teal(self, writer: "TealWriter") -> None: - if self.type == AVMType.int: + if stack_type(self.type) == AVMType.int: writer.write(self, f"pushint {self.value} // {self.name}") # type: ignore - elif self.type == AVMType.bytes: + elif stack_type(self.type) == AVMType.bytes: writer.write(self, f"pushbytes {self.value} // {self.name}") # type: ignore def _tealish(self) -> str: @@ -153,7 +159,7 @@ def __init__( def process(self) -> None: self.expression.process() - self.type = self.expression.type + self.type = self.expression.tealish_type() def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.expression) @@ -169,7 +175,7 @@ def __init__( self.name = name self.args = args self.parent = parent - self.type: Union[AVMType, List[AVMType]] = AVMType.none + self.type: Union[TealishType, List[TealishType]] = TealishType.none self.func_call_type: str = "" self.nodes = args self.immediate_args = "" @@ -235,7 +241,7 @@ def process_op_call(self, op: Op) -> None: def process_special_call(self) -> None: self.func_call_type = "special" if self.name == "pop": - self.type = AVMType.any + self.type = TealishType.any for arg in self.args: arg.process() @@ -275,7 +281,7 @@ def _tealish(self) -> str: class TxnField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -297,7 +303,7 @@ def __init__( ) -> None: self.field = field self.arrayIndex = arrayIndex - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -324,7 +330,7 @@ def __init__( ) -> None: self.field = field self.index = index - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -360,7 +366,7 @@ def __init__( self.field = field self.index = index self.arrayIndex = arrayIndex - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -403,7 +409,7 @@ def _tealish(self) -> str: class PositiveGroupIndex(BaseNode): def __init__(self, index: int, parent: Optional["BaseNode"] = None) -> None: self.index = index - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -418,7 +424,7 @@ def _tealish(self) -> str: class NegativeGroupIndex(BaseNode): def __init__(self, index: int, parent: Optional[BaseNode] = None) -> None: self.index = index - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -433,7 +439,7 @@ def _tealish(self) -> str: class GlobalField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -449,7 +455,7 @@ def _tealish(self) -> str: class InnerTxnField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -466,7 +472,7 @@ class StructOrBoxField(BaseNode): def __init__(self, name, field, parent=None) -> None: self.name = name self.field = field - self.type = AVMType.none + self.type = TealishType.none self.parent = parent def process(self) -> None: @@ -479,9 +485,9 @@ def process(self) -> None: self.type = struct_field.data_type def write_teal(self, writer: "TealWriter") -> None: - if self.object_type == ObjectType.struct: + if self.object_type == ObjectType.scratch: writer.write(self, f"load {self.slot} // {self.name}") - if self.type == AVMType.int: + if self.type == TealishType.int: writer.write(self, f"pushint {self.offset}") writer.write(self, f"extract_uint64 // {self.field}") else: @@ -491,7 +497,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.offset} // offset") writer.write(self, f"pushint {self.size} // size") writer.write(self, f"box_extract // {self.name}.{self.field}") - if self.type == AVMType.int: + if self.type == TealishType.int: writer.write(self, "btoi") else: raise Exception() diff --git a/tealish/langspec.py b/tealish/langspec.py index 4605b50..e32259a 100644 --- a/tealish/langspec.py +++ b/tealish/langspec.py @@ -3,25 +3,25 @@ import requests import tealish import json -from .tealish_builtins import constants, AVMType +from .tealish_builtins import constants, TealishType from typing import List, Dict, Any, Tuple, Optional abc = "ABCDEFGHIJK" _opcode_type_map = { - ".": AVMType.any, - "B": AVMType.bytes, - "U": AVMType.int, - "": AVMType.none, + ".": TealishType.any, + "B": TealishType.bytes, + "U": TealishType.int, + "": TealishType.none, } -def type_lookup(a: str) -> AVMType: +def type_lookup(a: str) -> TealishType: return _opcode_type_map[a] -def convert_args_to_types(args: str) -> List[AVMType]: +def convert_args_to_types(args: str) -> List[TealishType]: return [type_lookup(args[idx]) for idx in range(len(args))] @@ -35,11 +35,11 @@ class Op: #: list of arg types this op takes off the stack, encoded as a string args: str #: decoded list of incoming args - arg_types: List[AVMType] + arg_types: List[TealishType] #: list of arg types this op puts on the stack, encoded as a string returns: str #: decoded list of outgoing args - returns_types: List[AVMType] + returns_types: List[TealishType] #: how many bytes this opcode takes up when assembled size: int #: describes the args to be passed as immediate arguments to this op @@ -47,9 +47,9 @@ class Op: #: describes the list of names that can be used as immediate arguments arg_enum: List[str] #: describes the types returned when each arg enum is used - arg_enum_types: List[AVMType] + arg_enum_types: List[TealishType] #: dictionary mapping the names in arg_enum to types in arg_enum_types - arg_enum_dict: Dict[str, AVMType] + arg_enum_dict: Dict[str, TealishType] #: informational string about the op doc: str @@ -91,7 +91,7 @@ def __init__(self, op_def: Dict[str, Any]): if "ArgEnumTypes" in op_def: self.arg_enum_types = convert_args_to_types(op_def["ArgEnumTypes"]) else: - self.arg_enum_types = [AVMType.int] * len(self.arg_enum) + self.arg_enum_types = [TealishType.int] * len(self.arg_enum) self.arg_enum_dict = dict(zip(self.arg_enum, self.arg_enum_types)) else: self.arg_enum = [] @@ -126,8 +126,8 @@ def __init__(self, spec: Dict[str, Any]) -> None: "Txn": self.ops["txn"].arg_enum_dict, } - self.global_fields: Dict[str, AVMType] = self.fields["Global"] - self.txn_fields: Dict[str, AVMType] = self.fields["Txn"] + self.global_fields: Dict[str, TealishType] = self.fields["Global"] + self.txn_fields: Dict[str, TealishType] = self.fields["Txn"] def as_dict(self) -> Dict[str, Any]: return self.spec @@ -141,12 +141,12 @@ def lookup_op(self, name: str) -> Op: raise KeyError(f'Op "{name}" does not exist!') return self.ops[name] - def lookup_avm_constant(self, name: str) -> Tuple[AVMType, Any]: + def lookup_avm_constant(self, name: str) -> Tuple[TealishType, Any]: if name not in constants: raise KeyError(f'Constant "{name}" does not exist!') return constants[name] - def get_field_type(self, namespace: str, name: str) -> AVMType: + def get_field_type(self, namespace: str, name: str) -> TealishType: if "txn" in namespace: return self.txn_fields[name] elif namespace == "global": diff --git a/tealish/nodes.py b/tealish/nodes.py index ec2dfa0..32194fd 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -16,12 +16,14 @@ from .tx_expressions import parse_expression from .tealish_builtins import ( AVMType, + TealishType, ObjectType, define_struct, get_struct, VarType, Struct, StructField, + stack_type, ) from .scope import Scope @@ -158,9 +160,6 @@ class LiteralInt(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.value}") - def type(self) -> AVMType: - return AVMType.int - def _tealish(self) -> str: return f"{self.value}" @@ -172,9 +171,6 @@ class LiteralBytes(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushbytes {self.value}") - def type(self) -> AVMType: - return AVMType.bytes - def _tealish(self) -> str: return f"{self.value}" @@ -191,14 +187,10 @@ def __init__(self, line: str) -> None: def _tealish(self) -> str: return f"{self.value}" - def type(self) -> Optional[VarType]: - return self._type - class GenericExpression(Expression): - # TODO: never set? - type: str + type: TealishType @classmethod def parse(cls, line: str, parent: Node, compiler: "TealishCompiler") -> Node: @@ -373,16 +365,24 @@ def _tealish(self) -> str: class Const(LineStatement): + type_pattern = "|".join([f"\\b{tt.value}\\b" for tt in TealishType]) pattern = ( - r"const (?P\bint\b|\bbytes\b) " + rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" ) - type: AVMType + type: TealishType name: str expression: Literal def process(self) -> None: scope = self.get_current_scope() + + # TODO: have to compare against Enum.value ? + if self.type == TealishType.bigint.value: + # Hardcoded to uint256 or 32 bytes + new_value = cast(int, int(self.expression.value)).to_bytes(32, "big") + self.expression.value = f"0x{new_value.hex()}" + scope.declare_const(self.name, (self.type, self.expression.value)) def write_teal(self, writer: "TealWriter") -> None: @@ -455,7 +455,8 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() - if self.arg.type not in (AVMType.int, AVMType.any): + + if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " + f"Expected int, got {self.arg.type} at line {self.line_no}.", @@ -486,10 +487,10 @@ class BytesDeclaration(LineStatement): expression: GenericExpression def process(self) -> None: - self.name.slot = self.declare_var(self.name.value, AVMType.bytes) + self.name.slot = self.declare_var(self.name.value, TealishType.bytes) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.bytes, AVMType.any): raise CompileError( "Incorrect type for bytes assignment. " + f"Expected bytes, got {self.expression.type}", @@ -515,10 +516,10 @@ class IntDeclaration(LineStatement): expression: GenericExpression def process(self) -> None: - self.name.slot = self.declare_var(self.name.value, AVMType.int) + self.name.slot = self.declare_var(self.name.value, TealishType.int) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.int, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for int assignment. " + f"Expected int, got {self.expression.type}", @@ -547,7 +548,7 @@ class Assignment(LineStatement): def process(self) -> None: self.expression.process() t = self.expression.type - incoming_types = t if type(t) == list else [t] + incoming_types: List[TealishType] = t if type(t) == list else [t] # type: ignore names = [Name(s.strip()) for s in self.names.split(",")] self.name_nodes = names @@ -569,7 +570,10 @@ def process(self) -> None: ) slot, var_type = var_def - if not (incoming_types[i] == AVMType.any or incoming_types[i] == var_type): + if not ( + stack_type(incoming_types[i]) == AVMType.any + or stack_type(incoming_types[i]) == var_type + ): raise CompileError( f"Incorrect type for {var_type} assignment. " + f"Expected {var_type}, got {incoming_types[i]}", @@ -1277,7 +1281,7 @@ def consume(cls, compiler: "TealishCompiler", parent: Node) -> "ForStatement": return node def process(self) -> None: - self.var_slot = self.declare_var(self.var, AVMType.int) + self.var_slot = self.declare_var(self.var, TealishType.int) for n in self.nodes: n.process() self.del_var(self.var) @@ -1373,7 +1377,7 @@ def _tealish(self) -> str: class ArgsList(Expression): arg_pattern = r"(?P[a-z][a-z_0-9]*): (?Pint|bytes)" pattern = rf"(?P({arg_pattern}(, )?)*)" - args: List[Tuple[str, AVMType]] + args: List[Tuple[str, TealishType]] def __init__(self, line: str) -> None: super().__init__(line) @@ -1391,7 +1395,7 @@ class Func(InlineStatement): args: ArgsList return_type: str - returns: List[AVMType] + returns: List[TealishType] def __init__( self, @@ -1406,7 +1410,8 @@ def __init__( self.new_scope("func__" + self.name) self.returns = list( filter( - None, [cast(AVMType, s.strip()) for s in self.return_type.split(",")] + None, + [cast(TealishType, s.strip()) for s in self.return_type.split(",")], ) ) self.slots: Dict[str, int] = {} @@ -1501,7 +1506,7 @@ class StructFieldDefinition(InlineStatement): + r"(?P[a-z][A-Z-a-z0-9_]+)(\[(?P\d+)\])?" ) field_name: str - data_type: AVMType + data_type: TealishType data_length: int offset: int @@ -1575,7 +1580,7 @@ def process(self) -> None: data_length=field_node.data_length, offset=offset, size=8 - if field_node.data_type == AVMType.int + if stack_type(field_node.data_type) == AVMType.int else int(field_node.data_length), ) fields[field_node.field_name] = sf @@ -1608,11 +1613,11 @@ class StructDeclaration(LineStatement): def process(self) -> None: self.name.slot = self.declare_var( - self.name.value, (ObjectType.struct, self.struct_name) + self.name.value, (ObjectType.scratch, self.struct_name) ) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.bytes, AVMType.any): raise CompileError( "Incorrect type for struct assignment. " + f"Expected bytes, got {self.expression.type}", @@ -1659,7 +1664,10 @@ def process(self) -> None: self.size = struct_field.size self.data_type = struct_field.data_type self.expression.process() - if self.expression.type not in (self.data_type, AVMType.any): + if stack_type(self.expression.type) not in ( + stack_type(self.data_type), + AVMType.any, + ): raise CompileError( "Incorrect type for struct field assignment. " + f"Expected {self.data_type}, got {self.expression.type}", @@ -1667,11 +1675,11 @@ def process(self) -> None: ) def write_teal(self, writer: "TealWriter") -> None: - if self.object_type == ObjectType.struct: + if self.object_type == ObjectType.scratch: writer.write(self, f"// {self.line} [slot {self.name.slot}]") writer.write(self, f"load {self.name.slot} // {self.name.value}") writer.write(self, self.expression) - if self.data_type == AVMType.int: + if stack_type(self.data_type) == AVMType.int: writer.write(self, "itob") writer.write( self, f"replace {self.offset} // {self.name.value}.{self.field_name}" @@ -1682,7 +1690,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"load {self.name.slot} // box key {self.name.value}") writer.write(self, f"pushint {self.offset} // offset") writer.write(self, self.expression) - if self.data_type == AVMType.int: + if stack_type(self.data_type) == AVMType.int: writer.write(self, "itob") writer.write(self, f"box_replace // {self.name.value}.{self.field_name}") @@ -1717,7 +1725,7 @@ def process(self): self.name.value, (ObjectType.box, self.struct_name) ) self.key.process() - if self.key.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.key.type) not in (AVMType.bytes, AVMType.any): raise CompileError( f"Incorrect type for box key. Expected bytes, got {self.key.type}", node=self, diff --git a/tealish/scope.py b/tealish/scope.py index 645593e..b1218c6 100644 --- a/tealish/scope.py +++ b/tealish/scope.py @@ -2,7 +2,12 @@ if TYPE_CHECKING: - from .tealish_builtins import AVMType, VarType, ConstValue, ScratchRecord + from .tealish_builtins import ( + VarType, + ConstValue, + ScratchRecord, + TealishType, + ) from .nodes import Func, Block @@ -21,7 +26,7 @@ def __init__( slot_range if slot_range is not None else (0, 200) ) - self.consts: Dict[str, Tuple["AVMType", "ConstValue"]] = {} + self.consts: Dict[str, Tuple["TealishType", "ConstValue"]] = {} self.blocks: Dict[str, "Block"] = {} self.functions: Dict[str, "Func"] = {} @@ -59,11 +64,13 @@ def delete_var(self, name: str) -> None: del self.slots[name] def declare_const( - self, name: str, const_data: Tuple["AVMType", "ConstValue"] + self, + name: str, + const_data: Tuple["TealishType", "ConstValue"], ) -> None: self.consts[name] = const_data - def lookup_const(self, name: str) -> Tuple["AVMType", "ConstValue"]: + def lookup_const(self, name: str) -> Tuple["TealishType", "ConstValue"]: if name not in self.consts: raise KeyError(f'Const "{name}" not declared in current scope') return self.consts[name] diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index cd6aafe..fc68758 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -12,19 +12,40 @@ class AVMType(str, Enum): none = "" -# TODO: add frame ptr or stack? rename to something like `storage type?` -# I think `struct` here should probably just be `scratch`? class ObjectType(str, Enum): """ObjectType determines where to get the bytes for a struct field. - `struct` - the field is in a byte array in a scratch var, use extract to get bytes + `scratch` - the field is in a byte array in a scratch var, use extract to get bytes `box` - the field is in a box, use box_extract to get the bytes """ - struct = "struct" + scratch = "scratch" box = "box" +class TealishType(str, Enum): + # Mirrors AVM Types + any = "any" + bytes = "bytes" + int = "int" + none = "" + + # New types for tealish + bigint = "bigint" + addr = "addr" + + +def stack_type(tt: TealishType) -> AVMType: + if tt == TealishType.int.value: + return AVMType.int + elif tt == TealishType.none: + return AVMType.none + elif tt == TealishType.any: + return AVMType.any + else: + return AVMType.bytes + + # TODO: for CustomType and ScratchRecord we should consider # making them dataclasses or something instead of a tuple # to make it more obvious what the fields are @@ -36,7 +57,7 @@ class ObjectType(str, Enum): @dataclass class StructField: - data_type: AVMType + data_type: TealishType data_length: int offset: int size: int @@ -54,7 +75,7 @@ def __init__(self, fields: Dict[str, StructField], size: int): # either AVM native type or a CustomType (only struct atm) definition -VarType = Union[AVMType, CustomType] +VarType = Union[TealishType, CustomType] # a constant value introduced in source ConstValue = Union[str, bytes, int] @@ -74,16 +95,16 @@ def get_struct(struct_name: str) -> Struct: return _structs[struct_name] -constants: Dict[str, Tuple[AVMType, ConstValue]] = { - "NoOp": (AVMType.int, 0), - "OptIn": (AVMType.int, 1), - "CloseOut": (AVMType.int, 2), - "ClearState": (AVMType.int, 3), - "UpdateApplication": (AVMType.int, 4), - "DeleteApplication": (AVMType.int, 5), - "Pay": (AVMType.int, 1), - "Acfg": (AVMType.int, 3), - "Axfer": (AVMType.int, 4), - "Afrz": (AVMType.int, 5), - "Appl": (AVMType.int, 6), +constants: Dict[str, Tuple[TealishType, ConstValue]] = { + "NoOp": (TealishType.int, 0), + "OptIn": (TealishType.int, 1), + "CloseOut": (TealishType.int, 2), + "ClearState": (TealishType.int, 3), + "UpdateApplication": (TealishType.int, 4), + "DeleteApplication": (TealishType.int, 5), + "Pay": (TealishType.int, 1), + "Acfg": (TealishType.int, 3), + "Axfer": (TealishType.int, 4), + "Afrz": (TealishType.int, 5), + "Appl": (TealishType.int, 6), }