Types
Algorand Python exposes a number of types that provide a statically typed representation of the behaviour that is possible on the Algorand Virtual Machine.
AVM types
Section titled “AVM types”The most basic types on the AVM
are uint64 and bytes[], representing unsigned 64-bit integers and byte arrays respectively.
These are represented by UInt64 and Bytes in Algorand Python.
There are further “bounded” types supported by the AVM, which are backed by these two simple primitives.
For example, bigint represents a variably sized (up to 512-bits), unsigned integer, but is actually
backed by a bytes[]. This is represented by BigUInt in Algorand Python.
UInt64
Section titled “UInt64”algopy.UInt64 represents the underlying AVM uint64 type.
It supports all the same operators as int, except for /, you must use // for truncating
division instead.
# you can instantiate with an integer literalnum = algopy.UInt64(1)# no arguments default to the zero valuezero = algopy.UInt64()# zero is False, any other value is Trueassert not zeroassert num# Like Python's `int`, `UInt64` is immutable, so augmented assignment operators return new valuesone = numnum += 1assert one == 1assert num == 2# note that once you have a variable of type UInt64, you don't need to type any variables# derived from that or wrap int literalsnum2 = num + 200 // 3Further examples available here.
algopy.Bytes represents the underlying AVM bytes[] type. It is intended
to represent binary data, for UTF-8 it might be preferable to use String.
# you can instantiate with a bytes literaldata = algopy.Bytes(b"abc")# no arguments defaults to an empty valueempty = algopy.Bytes()# empty is False, non-empty is Trueassert dataassert not empty# Like Python's `bytes`, `Bytes` is immutable, augmented assignment operators return new valuesabc = datadata += b"def"assert abc == b"abc"assert data == b"abcdef"# indexing and slicing are supported, and both return a Bytesassert abc[0] == b"a"assert data[:3] == abc# check if a bytes sequence occurs within anotherassert abc in dataIndexing a Bytes returning a Bytes differs from the behaviour of Python’s bytes type, which
returns an int.
# you can iteratefor i in abc: ...# construct from encoded valuesbase32_seq = algopy.Bytes.from_base32('74======')base64_seq = algopy.Bytes.from_base64('RkY=')hex_seq = algopy.Bytes.from_hex('FF')# binary manipulations ^, &, |, and ~ are supporteddata ^= ~((base32_seq & base64_seq) | hex_seq)# access the length via the .length propertyassert abc.length == 3See Python builtins for an explanation of why len() isn’t supported.
String
Section titled “String”String is a special Algorand Python type that represents a UTF-8 encoded string.
It’s backed by Bytes, which can be accessed through the .bytes property.
It works similarly to Bytes, except that it works with str literals rather than bytes
literals. Additionally, due to a lack of AVM support for unicode data, indexing and length
operations are not currently supported (simply getting the length of a UTF-8 string is an O(N)
operation, which would be quite costly in a smart contract). If you are happy using the length as
the number of bytes, then you can call .bytes.length.
# you can instantiate with a string literaldata = algopy.String("abc")# no arguments defaults to an empty valueempty = algopy.String()# empty is False, non-empty is Trueassert dataassert not empty# Like Python's `str`, `String` is immutable, augmented assignment operators return new valuesabc = datadata += "def"assert abc == "abc"assert data == "abcdef"# whilst indexing and slicing are not supported, the following tests are:assert abc.startswith("ab")assert abc.endswith("bc")assert abc in data# you can also join multiple Strings together with a separator:assert algopy.String(", ").join((abc, abc)) == "abc, abc"# access the underlying bytesassert abc.bytes == b"abc"BigUInt
Section titled “BigUInt”algopy.BigUInt represents a variable length (max 512-bit) unsigned integer stored
as bytes[] in the AVM.
It supports all the same operators as int, except for power (**), left and right shift (<<
and >>) and / (as with UInt64, you must use // for truncating division instead).
Note that the op code costs for bigint math are an order of magnitude higher than those for
uint64 math. If you just need to handle overflow, take a look at the wide ops such as addw,
mulw, etc - all of which are exposed through the algopy.op module.
Another contrast between bigint and uint64 math is that bigint math ops don’t immediately
error on overflow - if the result exceeds 512-bits, then you can still access the value via
.bytes, but any further math operations will fail.
# you can instantiate with an integer literalnum = algopy.BigUInt(1)# no arguments default to the zero valuezero = algopy.BigUInt()# zero is False, any other value is Trueassert not zeroassert num# Like Python's `int`, `BigUInt` is immutable, so augmented assignment operators return new valuesone = numnum += 1assert one == 1assert num == UInt64(2)# note that once you have a variable of type BigUInt, you don't need to type any variables# derived from that or wrap int literalsnum2 = num + 200 // 3Further examples available here.
The semantics of the AVM bool bounded type exactly match the semantics of Python’s built-in bool type
and thus Algorand Python uses the in-built bool type from Python.
Per the behaviour in normal Python, Algorand Python automatically converts various types to bool when they
appear in statements that expect a bool e.g. if/while/assert statements, appear in Boolean expressions
(e.g. next to and or or keywords) or are explicitly casted to a bool.
The semantics of not, and and or are special per how these keywords work in Python
(e.g. short circuiting).
a = UInt64(1)b = UInt64(2)
c = a or bd = b and a
e = self.expensive_op(UInt64(0)) or self.side_effecting_op(UInt64(1))f = self.expensive_op(UInt64(3)) or self.side_effecting_op(UInt64(42))
g = self.side_effecting_op(UInt64(0)) and self.expensive_op(UInt64(42))h = self.side_effecting_op(UInt64(2)) and self.expensive_op(UInt64(3))
i = a if b < c else d + eif a: log("a is True")Further examples available here.
Account
Section titled “Account”Account represents a logical Account, backed by a bytes[32] representing the
bytes of the public key (without the checksum). It has various account related methods that can be called from the type.
Also see algopy.arc4.Address if needing to represent the address as a distinct type.
Asset represents a logical Asset, backed by a uint64 ID.
It has various asset related methods that can be called from the type.
Application
Section titled “Application”Application represents a logical Application, backed by a uint64 ID.
It has various application related methods that can be called from the type.
Python built-in types
Section titled “Python built-in types”Unfortunately, the AVM types don’t map to standard Python primitives. For instance,
in Python, an int is unsigned, and effectively unbounded. A bytes similarly is limited only by
the memory available, whereas an AVM bytes[] has a maximum length of 4096. In order to both maintain
semantic compatibility and allow for a framework implementation in plain Python that will fail under the
same conditions as when deployed to the AVM, support for Python primitives is limited.
In saying that, there are many places where built-in Python types can be used and over time the places these types can be used are expected to increase.
Per above Algorand Python has full support for bool.
Python tuples are supported as arguments to subroutines, local variables, return types.
typing.NamedTuple
Section titled “typing.NamedTuple”Python named tuples are also supported using typing.NamedTuple.
Default field values and subclassing a NamedTuple are not supported
import typing
import algopy
class Pair(typing.NamedTuple): foo: algopy.Bytes bar: algopy.BytesNone is not supported as a value, but is supported as a type annotation to indicate a function or subroutine
returns no value.
int, str, bytes, float
Section titled “int, str, bytes, float”The int, str and bytes built-in types are currently only supported as module-level constants or literals.
They can be passed as arguments to various Algorand Python methods that support them or
when interacting with certain AVM types e.g. adding a number to a UInt64.
float is not supported.
Template variables
Section titled “Template variables”Template variables can be used to represent a placeholder for a deploy-time provided
value. This can be declared using the TemplateVar[TYPE] type where TYPE is the
Algorand Python type that it will be interpreted as.
from algopy import BigUInt, Bytes, TemplateVar, UInt64, arc4from algopy.arc4 import UInt512
class TemplateVariablesContract(arc4.ARC4Contract): @arc4.abimethod() def get_bytes(self) -> Bytes: return TemplateVar[Bytes](docs/_build/markdown/"SOME_BYTES")
@arc4.abimethod() def get_big_uint(self) -> UInt512: x = TemplateVar[BigUInt](docs/_build/markdown/"SOME_BIG_UINT") return UInt512(x)
@arc4.baremethod(allow_actions=["UpdateApplication"]) def on_update(self) -> None: assert TemplateVar[bool](docs/_build/markdown/"UPDATABLE")
@arc4.baremethod(allow_actions=["DeleteApplication"]) def on_delete(self) -> None: assert TemplateVar[UInt64](docs/_build/markdown/"DELETABLE")The resulting TEAL code that PuyaPy emits has placeholders with TMPL_{template variable name}
that expects either an integer value or an encoded bytes value. This behaviour exactly
matches what
AlgoKit Utils expects.
For more information look at the API reference for TemplateVar.
ARC-4 types
Section titled “ARC-4 types”ARC-4 data types are a first class concept in Algorand Python. They can be passed into ARC-4
methods (which will translate to the relevant ARC-4 method signature), passed into subroutines, or
instantiated into local variables. A limited set of operations are exposed on some ARC-4 types, but
often it may make sense to convert the ARC-4 value to a native AVM type, in which case you can use
the native property to retrieve the value. Most of the ARC-4 types also allow for mutation e.g.
you can edit values in arrays by index.
Please see the reference documentation for the different classes that can be used to represent ARC-4 values or the ARC-4 documentation for more information about ARC-4.
Type Validation
Section titled “Type Validation”Most high-order types (i.e. not Uint64 or Bytes) supported by Algorand TypeScript exist as a single byte array value with a specific encoding. When reading one of these values from an untrusted source it is important to validate the encoding of this value before using it. For example when expecting a Account one should validate that there are exactly 32 bytes in the underlying value.
PuyaPy automatically validates some value sources for you, whilst leaving others to be explicitly validated by the developer. You should always validate untrusted sources (such as ABI args from untrusted clients) but may wish to omit validation for performance/efficiency reasons from trusted sources (such as a Global state value only your application accesses).
For more detailed information on the impacts of type validation refer to this section in the developer portal.
Validated Sources of Values
Section titled “Validated Sources of Values”The following sources of ABI values are always validated by the compiler by default.
- ABI method arguments (when called externally)
- ABI return values
- Bytes.to_fixed (with the
assert-lengthstrategy)
NOTE: Argument validation can be disabled globally via the --validate-abi-args flags. Similarly, return value validation can be disable via the --validate-abi-return flag. It is also possible for a method implementation to disable validation for its own arguments via the validate_encoding option on the abimethod decorator. Per-method argument validation settings override the global compiler settings. If one wishes to disable the return validation, you can parse the return value directly from the inner transaction’s last log and use an unsafe method (.from_bytes) for converting the bytes to the desired ABI type.
Non-Validated Sources
Section titled “Non-Validated Sources”There are certain places where one can get an ABI value that is not fully validated:
- Global state
- Local state
- Boxes
- Subroutine arguments
- Subroutine return values
from_bytesmethods on ABI types
There are no automatic validation steps taken for these values because it is assumed that the value was validated before reaching this point by the compiler.
For example, if a method takes an ABI value as an argument and stores it in a box, the value is validated when taken as input from the method arguments but not when placed in the box. By default, all sources of ABI values other than what is listed above does have ABI validation, thus it would be inefficient to perform validation again every time the value is used.
It should be noted, however, that all the validation methods the Puya compiler does automatically can be disabled on a per-method basis. This means it is theoretically possible for an incorrectly encoded value to come from one of the listed sources, but it will always be clear in the source code that this is the case.
For example, given the following contract:
class BoxReadWrite(ARC4Contract):
def __init__(self) -> None: self.acct_box = Box(Account)
@abimethod() def write_to_box(self, acct: Account) -> None: self.acct_box.value = acct
@abimethod() def read_from_box(self) -> Account: return self.acct_box.valueOne can be sure that the value in acctBox is always valid because the only source of the value is an ABI argument (acct in writeToBox). If validation was disabled, however, then one cannot trust that it is properly encoded and should perform a manual validation if required:
class BoxReadWrite(ARC4Contract):
def __init__(self): self.acct_box = Box(Account)
@abimethod(validate_encoding="unsafe_disabled") def write_to_box(self, acct: Account) -> None: acct.validate() self.acct_box.value = acct
@abimethod() def read_from_box(self) -> Account: return self.acct_box.valueSimilarly, if a the Account is constructed from bytes, a manual validation should be performed:
def write_to_box(self, acct_bytes: Bytes) -> None: acct = Account.from_bytes(acct_bytes) acct.validate() self.acct_box.value = acct