Periodic_Payment with PyTEAL
Writing Periodic_Payment Smart Contracts in Python
This is the documentation for my periodic_payment program submitted for Choice Coin Silver TEAL BASH task.
The Link to the code's github repo
In case you're wondering what TEAL and Pyteal are🤔, let's have look.
(Algorand Smart Contracts 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.
Whereas 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.
Going straight to the periodic payment PyTeal program. 🚀🚀🚀
from pyteal import *
"""Periodic Withdrawal"""
TMPL_rcv = Addr("6ZHGHH5Z5CTPCF5WCESXMGRSVK7QJETR63M3NY5FJCUYDHO57VTCMJOBGY") #address which is authorized to make withdrawals
TMPL_fee = Int(1000) #maximum fee used by the withdrawal transactioN
TMPL_period = Int(50) #the time between a pair of withdrawal periods
TMPL_dur = Int(5000) #the duration of a withdrawal period
TMPL_lease = Bytes("base64", "023sdDE2") #string to use for the transaction lease
TMPL_amt = Int(500) #the maximum number of funds allowed for a single withdrawal
TMPL_timeout = Int(30000) #maximum number of rounds to wait
def periodic_payment(
TMPL_fee=TMPL_fee,
TMPL_period=TMPL_period,
TMPL_dur=TMPL_dur,
TMPL_lease=TMPL_lease,
TMPL_amt=TMPL_amt,
TMPL_rcv=TMPL_rcv,
TMPL_timeout=TMPL_timeout,
):
# check the type of transaction. In this case, it is checking for a pay transaction.
periodic_pay_core = And(
Txn.type_enum() == TxnType.Payment,
Txn.fee() < TMPL_fee, #check the fee to ensure it is less than reasonable amount, So this says, make sure the transaction fee is less than or equal to 1000.
Txn.first_valid() % TMPL_period == Int(0),
Txn.last_valid() == TMPL_dur + Txn.first_valid(),
Txn.lease() == TMPL_lease,
)
periodic_pay_transfer = And(
Txn.close_remainder_to() == Global.zero_address(), #checks that the transaction set CloseRemainderTo to global zero address
Txn.rekey_to() == Global.zero_address(),
Txn.receiver() == TMPL_rcv, #set the receiver of the transaction to the intended receiver
Txn.amount() == TMPL_amt, #set the amount of the transaction to the intended amount.
Txn.first_valid() % TMPL_period == Int(0), #checks that the Txn.first_valid() is divisable by the TMPL_period
Txn.last_valid() == TMPL_dur + Txn.first_valid(), #checks Txn.first_valid() and Txn.last_valid() are Withdrawalyy_dur away.
Txn.lease() == TMPL_lease #Checks that the lease is set to limit replay rate
)
""" When the periodic payment’s time (Txn.first_valid() > TMPL_timeout),
then all the remaining balance is closed to TMPL_rcv .
"""
periodic_pay_close = And(
Txn.close_remainder_to() == TMPL_rcv,
Txn.rekey_to() == Global.zero_address(),
Txn.receiver() == Global.zero_address(),
Txn.first_valid() == TMPL_timeout,
Txn.amount() == Int(0),
)
periodic_pay_escrow = periodic_pay_core.And(
periodic_pay_transfer.Or(periodic_pay_close)
)
return periodic_pay_escrow
if __name__ == '__main__':
with open('periodic_payment.teal', 'w') as f: #write the teal code into periodic_payment.teal file
compiled = compileTeal(periodic_payment(), mode=Mode.Signature, version=2)
f.write(compiled)
The core of this program is found between lines 23 and 56 (In lines 5–11, we are simply defining constants).
Let’s take a look at periodic_pay_core :
periodic_pay_core = And(
Txn.type_enum() == TxnType.Payment,
Txn.fee() < TMPL_fee, #check the fee to ensure it is less than reasonable amount, So this says, make sure the transaction fee is less than or equal to 1000.
Txn.first_valid() % TMPL_period == Int(0),
Txn.last_valid() == TMPL_dur + Txn.first_valid(),
Txn.lease() == TMPL_lease,
)
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() == TxnType.Payment
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.
It also checks that 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.
periodic_pay_transfer = And(
Txn.close_remainder_to() == Global.zero_address(), #checks that the transaction set CloseRemainderTo to global zero address
Txn.rekey_to() == Global.zero_address(),
Txn.receiver() == TMPL_rcv, #set the receiver of the transaction to the intended receiver
Txn.amount() == TMPL_amt, #set the amount of the transaction to the intended amount.
Txn.first_valid() % TMPL_period == Int(0), #checks that the Txn.first_valid() is divisable by the TMPL_period
Txn.last_valid() == TMPL_dur + Txn.first_valid(), #checks Txn.first_valid() and Txn.last_valid() are Withdrawalyy_dur away.
Txn.lease() == TMPL_lease #Checks that the lease is set to limit replay rate
)
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.
Similarly, it allows the transfer to happen every TMPL_period rounds for tmpl_dur rounds by ALSO checking that the Txn.first_valid() is divisable by TMPL_period and Txn.first_valid() and Txn.last_valid() are TMPL_dur away.
periodic_pay_close = And(
Txn.close_remainder_to() == TMPL_rcv,
Txn.rekey_to() == Global.zero_address(),
Txn.receiver() == Global.zero_address(),
Txn.first_valid() == TMPL_timeout,
Txn.amount() == Int(0),
)
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 = periodic_pay_core.And( periodic_pay_transfer.Or(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 every TMPL_period rounds for TMPL_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, the FirstValid 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:
#pragma version 2
txn TypeEnum
int pay
==
txn Fee
int 1000
<
&&
txn FirstValid
int 50
%
int 0
==
&&
txn LastValid
int 5000
txn FirstValid
+
==
&&
txn Lease
byte base64(023sdDE2)
==
&&
txn CloseRemainderTo
global ZeroAddress
==
txn RekeyTo
global ZeroAddress
==
&&
txn Receiver
addr 6ZHGHH5Z5CTPCF5WCESXMGRSVK7QJETR63M3NY5FJCUYDHO57VTCMJOBGY
==
&&
txn Amount
int 500
==
&&
txn FirstValid
int 50
%
int 0
==
&&
txn LastValid
int 5000
txn FirstValid
+
==
&&
txn Lease
byte base64(023sdDE2)
==
&&
txn CloseRemainderTo
addr 6ZHGHH5Z5CTPCF5WCESXMGRSVK7QJETR63M3NY5FJCUYDHO57VTCMJOBGY
==
txn RekeyTo
global ZeroAddress
==
&&
txn Receiver
global ZeroAddress
==
&&
txn FirstValid
int 30000
==
&&
txn Amount
int 0
==
&&
||
&&
return