PyTeal — Writing Algorand Smart Contracts in Python
PyTeal is a python language binding for Algorand Smart Contracts (ASC) that abstracts away the complexities in writing smart contracts. PyTeal is provided as an open-source tool for the Algorand community. We invite you to try, use, and contribute to PyTeal if you are interested in developing and deploying ASC’s in Python.
Why a Language Binding?
In the 2.0 release, we added ASC as the high performance layer-1 smart contract primitive to Algorand.
ASC’s are written in a byte-code based stack language called Transaction Execution Approval Language (TEAL). TEAL can analyze and approve transactions but it cannot create or change transactions, and as a result returns only true or false.
TEAL was designed to be significantly limited in relation to what most the world thinks of when the term “smart contract” is used. The language is a non “turing-complete” language, that does not support looping but does support forward branches, it consists of 30 basic instructions, it executes as a straight-line program solely using TEAL operations and every ASC is at most 1KB-long.
However, there is significant overhead in writing logic using a stack language. There is also the added overhead in needing to learn yet another blockchain language. PyTeal allows Algorand developers to express their smart contract logic in Python. The PyTeal library then compiles the smart contract logic to TEAL source code for the developers. In a nutshell, PyTeal abstracts away a lot of heavy lifting that is required when writing TEAL.
A PyTeal Example
Let’s take a look at a periodic payment PyTeal program.
The core of this example is found between lines 14 and 30 (In lines 6–12, we are simply defining constants).
Let’s take a look at
The first thing this program does is check the type of transaction. In this case, it is checking for a pay transaction.
Txn.type_enum() == Int(1)
Below is the list of TypeEnums:
The next thing the program does is check the fee to make sure that it is less than some reasonable amount. This should always be done as a security and sanity check because otherwise, someone could send a transaction that the contract might approve where all of money in the contract is wasted on the transaction fee.
Txn.fee() <= tmpl_fee,
So this says, make sure the transaction fee is less than or equal to 1000.
periodic_pay_transfer checks that the transaction set
CloseRemainderTo to global zero address (which means the transaction will not be closing out the balance of the address), set the receiver of the transaction to the intended receiver, and set the amount of the transaction to the intended amount.
In addition, it allows the transfer to happen every
tmpl_period rounds for
tmpl_dur rounds by checking that the
Txn.first_valid() is divisable by
Last, we check that the lease is set to limit replay rate. Which means that only one of these transactions can hold this lease between the first and last valid rounds. This parameter is a 32-byte string and, combined with the sender field, makes the transaction unique. If anyone resends the transaction with the same lease parameter, and sender, within the same window of the first and last valid rounds, the transaction will be rejected.
The final main section of PyTeal code specifies that close case. When the periodic payment’s time (
Txn.first_valid() > tmpl_timeout), then all the remaining balance is closed to
Now that all the conditions are declared, we can piece together all the conditions (
periodic_pay_transfer , and
periodic_pay_close ) in
periodic_pay_escrow = And(periodic_pay_core, Or(periodic_pay_transfer, periodic_pay_close))
periodic_pay_escrow is saying, here are the conditions for the core of this payment transaction :
- it needs to be a payment transaction, and it needs to be less than or equal than
AND one of the following:
- make sure the the
CloseRemainderTois set to 0, so that there isn’t an accidental exit for the funds that are in the account, check the receiver, check the amount, check that this transaction happens every
tmpl_durrounds, check that lease is set properly
- check that the
CloseRemainderTofield is set to the receiver, make sure the receiver field is now 0, the
FirstValidof the transaction has passed the timeout round (
tmpl_timeout) round and that the amount is also 0.
Running this python code will produce the following TEAL source:
Deploy Smart Contracts Constructed by PyTeal
Now that we’ve seen PyTeal and understand how it compiles to TEAL source, let’s run the example.
Before running this PyTeal code, make sure that you generate an arbitrary 32 byte string for the lease parameter and pass in that same string into the Python SDK code you are using to sign and send the logic. You can use the one in the PyTeal code above if you are just doing a practice run of this code.
- Go ahead and import the PyTeal code from the gist at the top of the post into your editor of choice.
pip3 install pyteal
Now, assume you have installed Algorand Python SDK and Algorand command line tool goal. We can run the following Python code (assuming the PyTeal code is in periodic_payment.py under same folder):
The code generates output like:
Woohoo! You deployed a periodic payment smart contract!
PyTeal Feature Highlights
Next, we show some more interesting features provided by PyTeal:
Overloading Python Operators
PyTeal overloads Python’s arithmetic and comparison operators (+, -, *, /, >, <, >=, >=, ==) so that PyTeal users can express smart contract logic in Python more naturally. For example:
Txn.fee() < Int(4000)
checks that the transaction fee of current transaction is lower than 4000 microAlgos.
Gtxn.amount(0) == Gtxn.amount(1) * Int(2)
checks that the amount of Algos transferred in the first transaction in the group transfer is twice the algos transferred in the second transaction.
In TEAL, there are two data types:
uint64, an unsigned 64-bit integer type, and
bytes, a raw bytes type. Each TEAL operator’s input must be properly typed. Otherwise, there will be a runtime error if that part of TEAL code is executed. In PyTeal, we implement a type checking sub-system that will reject wrongly typed PyTeal programs at the time of constructing PyTeal program. For example,
"""type error"""cond = Txn.fee() < Txn.receiver()
Running this PyTeal program in Python yields an error message:
TealTypeError: Type error: TealType.bytes while expected TealType.uint64
This is because
Txn.receiver() is typed
bytes while the overloaded
uint64 typed operands on both sides.
High Level Abstractions
PyTeal also provides “sugared ” higher level abstractions that capture use patterns to make developing in PyTeal even more “sweet”. For example, we observed that a common use pattern of TEAL is to switch on different cases when the transaction should be approved (like the receive case and escape case in hash time locked contract). We create a
Cond operator in PyTeal to make expressing such kinds of conditions much easier. For example:
dutch = Cond([Global.group_size() == Int(5), bid], [Global.group_size() == Int(4), redeem], [Global.group_size() == Int(1), wrapup])
Cond takes a sequence of
[condition, action] pairs, and evaluates to the first
action whose associated
condition evaluates to
true. In the example above, if the group size of the current Algorand atomic transfer is 5,
bid is evaluated; if the group size is 4,
redeem is evaluated; if the group size is 1,
wrapup will be evaluated. If the group size is none of these above, the generated TEAL code will return an error.
Thanks for giving this a read! Please consider using and contributing to PyTeal if you are interested in developing and deploying Algorand smart contracts in Python.
Victor Luchangco, Sam Abbassi, Jason Weathersby, and Derek Leung also contributed to this article. The periodic payment contract is from the original TEAL periodic payment contract by Derek Leung and Max Justicz.
Original article published on Medium: https://medium.com/algorand/pyteal-writing-algorand-smart-contracts-in-python-acfd7f7a48dd