diff --git a/Favor_Pyteal/Readme.md b/Favor_Pyteal/Readme.md new file mode 100644 index 0000000..9c6265e --- /dev/null +++ b/Favor_Pyteal/Readme.md @@ -0,0 +1,22 @@ +# Periodic_payment + +## Periodic Payment allows some account to execute periodic release of funds after a period and number of rounds + +This allows TMPL_RCV to withdraw TMPL_AMT every + +TMPL_PERIOD rounds for TMPL_DUR after every multiple +of TMPL_PERIOD. + +After TMPL_TIMEOUT, all remaining funds in the escrow +are available to TMPL_RCV. + +### Install + +- PyTeal requires Python version >= 3.6. +- cd to the directory where requirements.txt is located. +- activate your virtualenv. +- run: pip install -r requirements.txt in your shell. + +### Run + +- Run the periodic_transfer contract in your shell diff --git a/Favor_Pyteal/contract.py b/Favor_Pyteal/contract.py new file mode 100644 index 0000000..399cab2 --- /dev/null +++ b/Favor_Pyteal/contract.py @@ -0,0 +1,49 @@ +# This example is provided for informational purposes only and has not been audited for security. + +from pyteal import * + +"""Periodic Payment""" + +tmpl_fee = Int(1000) +tmpl_period = Int(50) +tmpl_dur = Int(5000) +tmpl_lease = Bytes("base64", "023sdDE2") +tmpl_amt = Int(2000) +tmpl_rcv = Addr("6ZHGHH5Z5CTPCF5WCESXMGRSVK7QJETR63M3NY5FJCUYDHO57VTCMJOBGY") +tmpl_timeout = Int(30000) + +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): + + periodic_pay_core = And( + Txn.type_enum() == TxnType.Payment, + Txn.fee() < tmpl_fee, + 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(), + Txn.receiver() == tmpl_rcv, + Txn.amount() == tmpl_amt + ) + + periodic_pay_close = And( + Txn.close_remainder_to() == tmpl_rcv, + 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__": + print(compileTeal(periodic_payment(), Mode.Signature)) \ No newline at end of file diff --git a/Favor_Pyteal/deploy.py b/Favor_Pyteal/deploy.py new file mode 100644 index 0000000..3b98b58 --- /dev/null +++ b/Favor_Pyteal/deploy.py @@ -0,0 +1,249 @@ +import base64 + +from algosdk.future import transaction +from algosdk import account, mnemonic, logic +from algosdk.v2client import algod +from pyteal import * + +# user declared account mnemonics +creator_mnemonic = "avocado assault awake sea public curious exit valve donkey tired escape dash drink diagram section avocado assault awake sea public curious exit valve donkey tired escape dash drink diagram section absent cruise" +# user declared algod connection parameters. Node must have EnableDeveloperAPI set to true in its config +algod_address = "http://localhost:4001" +algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +# helper function to compile program source +def compile_program(client, source_code): + compile_response = client.compile(source_code) + return base64.b64decode(compile_response['result']) + +# helper function that converts a mnemonic passphrase into a private signing key +def get_private_key_from_mnemonic(mn) : + private_key = mnemonic.to_private_key(mn) + return private_key + +# helper function that waits for a given txid to be confirmed by the network +def wait_for_confirmation(client, transaction_id, timeout): + """ + Wait until the transaction is confirmed or rejected, or until 'timeout' + number of rounds have passed. + Args: + transaction_id (str): the transaction to wait for + timeout (int): maximum number of rounds to wait + Returns: + dict: pending transaction information, or throws an error if the transaction + is not confirmed or rejected in the next timeout rounds + """ + start_round = client.status()["last-round"] + 1 + current_round = start_round + + while current_round < start_round + timeout: + try: + pending_txn = client.pending_transaction_info(transaction_id) + except Exception: + return + if pending_txn.get("confirmed-round", 0) > 0: + return pending_txn + elif pending_txn["pool-error"]: + raise Exception( + 'pool error: {}'.format(pending_txn["pool-error"])) + client.status_after_block(current_round) + current_round += 1 + raise Exception( + 'pending tx not found in timeout rounds, timeout value = : {}'.format(timeout)) + +# helper function that formats global state for printing +def format_state(state): + formatted = {} + for item in state: + key = item['key'] + value = item['value'] + formatted_key = base64.b64decode(key).decode('utf-8') + if value['type'] == 1: + # byte string + if formatted_key == 'voted': + formatted_value = base64.b64decode(value['bytes']).decode('utf-8') + else: + formatted_value = value['bytes'] + formatted[formatted_key] = formatted_value + else: + # integer + formatted[formatted_key] = value['uint'] + return formatted + +# helper function to read app global state +def read_global_state(client, addr, app_id): + results = client.account_info(addr) + apps_created = results['created-apps'] + for app in apps_created: + if app['id'] == app_id: + return format_state(app['params']['global-state']) + return {} + + +"""Basic Counter Application in PyTeal""" + +def approval_program(): + on_creation = Seq([ + App.globalPut(Bytes("Count"), Int(0)), + Return(Int(1)) + ]) + + handle_optin = Return(Int(0)) + + handle_closeout = Return(Int(0)) + + handle_updateapp = Return(Int(0)) + + handle_deleteapp = Return(Int(0)) + + scratchCount = ScratchVar(TealType.uint64) + + add = Seq([ + scratchCount.store(App.globalGet(Bytes("Count"))), + App.globalPut(Bytes("Count"), scratchCount.load() + Int(1)), + Return(Int(1)) + ]) + + deduct = Seq([ + scratchCount.store(App.globalGet(Bytes("Count"))), + If(scratchCount.load() > Int(0), + App.globalPut(Bytes("Count"), scratchCount.load() - Int(1)), + ), + Return(Int(1)) + ]) + + handle_noop = Cond( + [And( + Global.group_size() == Int(1), + Txn.application_args[0] == Bytes("Add") + ), add], + [And( + Global.group_size() == Int(1), + Txn.application_args[0] == Bytes("Deduct") + ), deduct], + ) + + program = Cond( + [Txn.application_id() == Int(0), on_creation], + [Txn.on_completion() == OnComplete.OptIn, handle_optin], + [Txn.on_completion() == OnComplete.CloseOut, handle_closeout], + [Txn.on_completion() == OnComplete.UpdateApplication, handle_updateapp], + [Txn.on_completion() == OnComplete.DeleteApplication, handle_deleteapp], + [Txn.on_completion() == OnComplete.NoOp, handle_noop] + ) + # Mode.Application specifies that this is a smart contract + return compileTeal(program, Mode.Application, version=5) + +def clear_state_program(): + program = Return(Int(1)) + # Mode.Application specifies that this is a smart contract + return compileTeal(program, Mode.Application, version=5) + + +# create new application +def create_app(client, private_key, approval_program, clear_program, global_schema, local_schema): + # define sender as creator + sender = account.address_from_private_key(private_key) + + # declare on_complete as NoOp + on_complete = transaction.OnComplete.NoOpOC.real + + # get node suggested parameters + params = client.suggested_params() + + # create unsigned transaction + txn = transaction.ApplicationCreateTxn(sender, params, on_complete, \ + approval_program, clear_program, \ + global_schema, local_schema) + + # sign transaction + signed_txn = txn.sign(private_key) + tx_id = signed_txn.transaction.get_txid() + + # send transaction + client.send_transactions([signed_txn]) + + # await confirmation + wait_for_confirmation(client, tx_id, 5) + + # display results + transaction_response = client.pending_transaction_info(tx_id) + app_id = transaction_response['application-index'] + print("Created new app-id:", app_id) + + return app_id + + +# call application +def call_app(client, private_key, index, app_args) : + # declare sender + sender = account.address_from_private_key(private_key) + + # get node suggested parameters + params = client.suggested_params() + + # create unsigned transaction + txn = transaction.ApplicationNoOpTxn(sender, params, index, app_args) + + # sign transaction + signed_txn = txn.sign(private_key) + tx_id = signed_txn.transaction.get_txid() + + # send transaction + client.send_transactions([signed_txn]) + + # await confirmation + wait_for_confirmation(client, tx_id, 5) + + print("Application called") + +def main() : + # initialize an algodClient + algod_client = algod.AlgodClient(algod_token, algod_address) + + # define private keys + creator_private_key = get_private_key_from_mnemonic(creator_mnemonic) + + # declare application state storage (immutable) + local_ints = 0 + local_bytes = 0 + global_ints = 1 + global_bytes = 0 + global_schema = transaction.StateSchema(global_ints, global_bytes) + local_schema = transaction.StateSchema(local_ints, local_bytes) + + # compile program to TEAL assembly + with open("./approval.teal", "w") as f: + approval_program_teal = approval_program() + f.write(approval_program_teal) + + + # compile program to TEAL assembly + with open("./clear.teal", "w") as f: + clear_state_program_teal = clear_state_program() + f.write(clear_state_program_teal) + + # compile program to binary + approval_program_compiled = compile_program(algod_client, approval_program_teal) + + # compile program to binary + clear_state_program_compiled = compile_program(algod_client, clear_state_program_teal) + + print("--------------------------------------------") + print("Deploying Counter application......") + + # create new application + app_id = create_app(algod_client, creator_private_key, approval_program_compiled, clear_state_program_compiled, global_schema, local_schema) + + # read global state of application + print("Global state:", read_global_state(algod_client, account.address_from_private_key(creator_private_key), app_id)) + + print("--------------------------------------------") + print("Calling Counter application......") + app_args = ["Add"] + call_app(algod_client, creator_private_key, app_id, app_args) + + # read global state of application + print("Global state:", read_global_state(algod_client, account.address_from_private_key(creator_private_key), app_id)) + +main() \ No newline at end of file