PyTeal — Writing Algorand Smart Contracts in Python
Warning
The PyTeal template used in this example is now insecure following the release of Algorand’s rekeying feature. Please be sure to consult the new version of this pyteal template for the secure implementation.
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 periodic_pay_core
:
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:
https://developer.algorand.org/docs/reference/teal/opcodes/
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.
Similarly, 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 tmpl_period
and Txn.first_valid()
and Txn.last_valid()
are tmpl_dur
away.
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 tmpl_rcv
.
Now that all the conditions are declared, we can piece together all the conditions (periodic_pay_core
, periodic_pay_transfer
, and periodic_pay_close
) in periodic_pay_escrow
:
periodic_pay_escrow = And(periodic_pay_core,
Or(periodic_pay_transfer,
periodic_pay_close))
In totality, periodic_pay_escrow
is saying, here are the conditions for the core of this payment transaction :
periodic_pay_core
:
- it needs to be a payment transaction, and it needs to be less than or equal than
tmpl_fee
AND one of the following:
periodic_pay_transfer
:
- make sure the the
CloseRemainderTo
is 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 everytmpl_periodrounds
fortmpl_dur
rounds, check that lease is set properly
OR
periodic_pay_close
:
- check that the
CloseRemainderTo
field is set to the receiver, make sure the receiver field is now 0, theFirstValid
of 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.
We will be using goal to compile TEAL and Algorand Python SDK to construct and submit the transaction.
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
python3 periodic_payment.py
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.
Type Checking
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 <
expect 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.
Conclusion
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.
More Resources
PyTeal GitHub: https://github.com/algorand/pyteal
PyTeal Documentation: https://pyteal.readthedocs.io/en/latest/
Algorand User Forum: https://forum.algorand.org/
Acknowledgements
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