From 3aab0e48fe57e29831e66120b1d54cf88b849013 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 7 Nov 2022 10:32:30 -0800 Subject: [PATCH 1/6] Restructure ABI examples --- .flake8 | 3 +- examples/application/abi/algobank/__init__.py | 0 .../abi/{ => algobank}/algobank.json | 0 .../abi/{ => algobank}/algobank.py | 0 .../abi/{ => algobank}/algobank_approval.teal | 0 .../{ => algobank}/algobank_clear_state.teal | 0 examples/application/abi/vote/__init__.py | 0 examples/application/abi/vote/approval.teal | 3 ++ .../application/abi/vote/clear_state.teal | 3 ++ examples/application/abi/vote/contract.json | 5 +++ examples/application/abi/vote/contract.py | 19 +++++++++++ tests/unit/compile_test.py | 32 +++++++++++++++++-- 12 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 examples/application/abi/algobank/__init__.py rename examples/application/abi/{ => algobank}/algobank.json (100%) rename examples/application/abi/{ => algobank}/algobank.py (100%) rename examples/application/abi/{ => algobank}/algobank_approval.teal (100%) rename examples/application/abi/{ => algobank}/algobank_clear_state.teal (100%) create mode 100644 examples/application/abi/vote/__init__.py create mode 100644 examples/application/abi/vote/approval.teal create mode 100644 examples/application/abi/vote/clear_state.teal create mode 100644 examples/application/abi/vote/contract.json create mode 100644 examples/application/abi/vote/contract.py diff --git a/.flake8 b/.flake8 index 262d4b172..cde33a757 100644 --- a/.flake8 +++ b/.flake8 @@ -10,7 +10,8 @@ ignore = per-file-ignores = pyteal/compiler/optimizer/__init__.py: F401 - examples/application/abi/algobank.py: F403, F405 + examples/application/abi/algobank/algobank.py: F403, F405 + examples/application/abi/vote/contract.py: F403, F405 examples/application/asset.py: F403, F405 examples/application/opup.py: F403, F405 examples/application/security_token.py: F403, F405 diff --git a/examples/application/abi/algobank/__init__.py b/examples/application/abi/algobank/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/application/abi/algobank.json b/examples/application/abi/algobank/algobank.json similarity index 100% rename from examples/application/abi/algobank.json rename to examples/application/abi/algobank/algobank.json diff --git a/examples/application/abi/algobank.py b/examples/application/abi/algobank/algobank.py similarity index 100% rename from examples/application/abi/algobank.py rename to examples/application/abi/algobank/algobank.py diff --git a/examples/application/abi/algobank_approval.teal b/examples/application/abi/algobank/algobank_approval.teal similarity index 100% rename from examples/application/abi/algobank_approval.teal rename to examples/application/abi/algobank/algobank_approval.teal diff --git a/examples/application/abi/algobank_clear_state.teal b/examples/application/abi/algobank/algobank_clear_state.teal similarity index 100% rename from examples/application/abi/algobank_clear_state.teal rename to examples/application/abi/algobank/algobank_clear_state.teal diff --git a/examples/application/abi/vote/__init__.py b/examples/application/abi/vote/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/application/abi/vote/approval.teal b/examples/application/abi/vote/approval.teal new file mode 100644 index 000000000..ba95445f2 --- /dev/null +++ b/examples/application/abi/vote/approval.teal @@ -0,0 +1,3 @@ +#pragma version 8 +int 0 +return \ No newline at end of file diff --git a/examples/application/abi/vote/clear_state.teal b/examples/application/abi/vote/clear_state.teal new file mode 100644 index 000000000..ba95445f2 --- /dev/null +++ b/examples/application/abi/vote/clear_state.teal @@ -0,0 +1,3 @@ +#pragma version 8 +int 0 +return \ No newline at end of file diff --git a/examples/application/abi/vote/contract.json b/examples/application/abi/vote/contract.json new file mode 100644 index 000000000..66052b39f --- /dev/null +++ b/examples/application/abi/vote/contract.json @@ -0,0 +1,5 @@ +{ + "name": "Vote", + "methods": [], + "networks": {} +} \ No newline at end of file diff --git a/examples/application/abi/vote/contract.py b/examples/application/abi/vote/contract.py new file mode 100644 index 000000000..62df47fd9 --- /dev/null +++ b/examples/application/abi/vote/contract.py @@ -0,0 +1,19 @@ +# This example is provided for informational purposes only and has not been audited for security. +from pyteal import * +import json + +router = Router(name="Vote") + +approval_program, clear_state_program, contract = router.compile_program( + version=8, optimize=OptimizeOptions(scratch_slots=True) +) + +if __name__ == "__main__": + with open("approval.teal", "w") as f: + f.write(approval_program) + + with open("clear_state.teal", "w") as f: + f.write(clear_state_program) + + with open("contract.json", "w") as f: + f.write(json.dumps(contract.dictify(), indent=4)) diff --git a/tests/unit/compile_test.py b/tests/unit/compile_test.py index 427c7e4c6..842053992 100644 --- a/tests/unit/compile_test.py +++ b/tests/unit/compile_test.py @@ -6,13 +6,13 @@ def test_abi_algobank(): - from examples.application.abi.algobank import ( + from examples.application.abi.algobank.algobank import ( approval_program, clear_state_program, contract, ) - target_dir = Path.cwd() / "examples" / "application" / "abi" + target_dir = Path.cwd() / "examples" / "application" / "abi" / "algobank" with open( target_dir / "algobank_approval.teal", "r" @@ -35,6 +35,34 @@ def test_abi_algobank(): assert contract.dictify() == expected_contract +def test_abi_vote(): + from examples.application.abi.vote.contract import ( + approval_program, + clear_state_program, + contract, + ) + + target_dir = Path.cwd() / "examples" / "application" / "abi" / "vote" + + with open(target_dir / "approval.teal", "r") as expected_approval_program_file: + expected_approval_program = "".join( + expected_approval_program_file.readlines() + ).strip() + assert approval_program == expected_approval_program + + with open( + target_dir / "clear_state.teal", "r" + ) as expected_clear_state_program_file: + expected_clear_state_program = "".join( + expected_clear_state_program_file.readlines() + ).strip() + assert clear_state_program == expected_clear_state_program + + with open(target_dir / "contract.json", "r") as expected_contract_file: + expected_contract = json.load(expected_contract_file) + assert contract.dictify() == expected_contract + + def test_basic_bank(): from examples.signature.basic import bank_for_account From faca94de40f00f0508d03862ee071acc0a58d491 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 7 Nov 2022 11:19:47 -0800 Subject: [PATCH 2/6] Fix docs --- docs/abi.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/abi.rst b/docs/abi.rst index 63d65147c..85256af93 100644 --- a/docs/abi.rst +++ b/docs/abi.rst @@ -916,12 +916,12 @@ In addition to receiving the approval and clear state programs, the :any:`Router Here's an example of a complete application that uses the :any:`Router` class: -.. literalinclude:: ../examples/application/abi/algobank.py +.. literalinclude:: ../examples/application/abi/algobank/algobank.py :language: python This example uses the :code:`Router.compile_program` method to create the approval program, clear state program, and contract description for the "AlgoBank" contract. The produced :code:`algobank.json` file is below: -.. literalinclude:: ../examples/application/abi/algobank.json +.. literalinclude:: ../examples/application/abi/algobank/algobank.json :language: json Calling an ARC-4 Program From 9211ecdeda7fe55f2204bfa7d4e3da71c409e343 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 7 Nov 2022 13:53:44 -0800 Subject: [PATCH 3/6] Basic app skeleton --- .../abi/{vote => poll}/__init__.py | 0 examples/application/abi/poll/approval.teal | 169 ++++++++++++++++++ .../abi/{vote => poll}/clear_state.teal | 0 examples/application/abi/poll/contract.json | 65 +++++++ examples/application/abi/poll/contract.py | 107 +++++++++++ examples/application/abi/vote/approval.teal | 3 - examples/application/abi/vote/contract.json | 5 - examples/application/abi/vote/contract.py | 19 -- 8 files changed, 341 insertions(+), 27 deletions(-) rename examples/application/abi/{vote => poll}/__init__.py (100%) create mode 100644 examples/application/abi/poll/approval.teal rename examples/application/abi/{vote => poll}/clear_state.teal (100%) create mode 100644 examples/application/abi/poll/contract.json create mode 100644 examples/application/abi/poll/contract.py delete mode 100644 examples/application/abi/vote/approval.teal delete mode 100644 examples/application/abi/vote/contract.json delete mode 100644 examples/application/abi/vote/contract.py diff --git a/examples/application/abi/vote/__init__.py b/examples/application/abi/poll/__init__.py similarity index 100% rename from examples/application/abi/vote/__init__.py rename to examples/application/abi/poll/__init__.py diff --git a/examples/application/abi/poll/approval.teal b/examples/application/abi/poll/approval.teal new file mode 100644 index 000000000..ae45c7bdf --- /dev/null +++ b/examples/application/abi/poll/approval.teal @@ -0,0 +1,169 @@ +#pragma version 8 +txn NumAppArgs +int 0 +== +bnz main_l12 +txna ApplicationArgs 0 +method "create(string[],bool)void" +== +bnz main_l11 +txna ApplicationArgs 0 +method "open()void" +== +bnz main_l10 +txna ApplicationArgs 0 +method "close()void" +== +bnz main_l9 +txna ApplicationArgs 0 +method "submit(uint16)void" +== +bnz main_l8 +txna ApplicationArgs 0 +method "status()(bool,bool,(string,uint64)[])" +== +bnz main_l7 +err +main_l7: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +callsub status_4 +store 2 +byte 0x151f7c75 +load 2 +concat +log +int 1 +return +main_l8: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +txna ApplicationArgs 1 +int 0 +extract_uint16 +callsub submit_3 +int 1 +return +main_l9: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +callsub close_2 +int 1 +return +main_l10: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +!= +&& +assert +callsub open_1 +int 1 +return +main_l11: +txn OnCompletion +int NoOp +== +txn ApplicationID +int 0 +== +&& +assert +txna ApplicationArgs 1 +store 0 +txna ApplicationArgs 2 +int 0 +int 8 +* +getbit +store 1 +load 0 +load 1 +callsub create_0 +int 1 +return +main_l12: +txn OnCompletion +int DeleteApplication +== +bnz main_l14 +err +main_l14: +txn ApplicationID +int 0 +!= +assert +txn Sender +global CreatorAddress +== +assert +int 1 +return + +// create +create_0: +store 8 +store 7 +retsub + +// open +open_1: +retsub + +// close +close_2: +retsub + +// submit +submit_3: +store 9 +retsub + +// status +status_4: +int 0 +store 3 +int 0 +store 4 +int 0 +itob +extract 6 0 +byte "" +concat +store 5 +byte 0x00 +int 0 +load 3 +setbit +int 1 +load 4 +setbit +load 5 +store 6 +int 3 +itob +extract 6 0 +concat +load 6 +concat +retsub \ No newline at end of file diff --git a/examples/application/abi/vote/clear_state.teal b/examples/application/abi/poll/clear_state.teal similarity index 100% rename from examples/application/abi/vote/clear_state.teal rename to examples/application/abi/poll/clear_state.teal diff --git a/examples/application/abi/poll/contract.json b/examples/application/abi/poll/contract.json new file mode 100644 index 000000000..ea2717a94 --- /dev/null +++ b/examples/application/abi/poll/contract.json @@ -0,0 +1,65 @@ +{ + "name": "OpenPollingApp", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "string[]", + "name": "options", + "desc": "A list of options for the poll. This list should not contain duplicate entries." + }, + { + "type": "bool", + "name": "can_resubmit", + "desc": "Whether this poll allows accounts to change their submissions or not." + } + ], + "returns": { + "type": "void" + }, + "desc": "Create a new polling application." + }, + { + "name": "open", + "args": [], + "returns": { + "type": "void" + }, + "desc": "Marks this poll as open.\nThis will fail if the poll is already open.\nThe poll must be open in order to receive user input." + }, + { + "name": "close", + "args": [], + "returns": { + "type": "void" + }, + "desc": "Marks this poll as closed.\nThis will fail if the poll is already closed." + }, + { + "name": "submit", + "args": [ + { + "type": "uint16", + "name": "choice", + "desc": "The choice made by the sender. This must be an index into the options for this poll." + } + ], + "returns": { + "type": "void" + }, + "desc": "Submit a response to the poll.\nSubmissions can only be received if the poll is open. If the poll is closed, this will fail.\nIf a submission has already been made by the sender and the poll allows resubmissions, the sender's choice will be updated to the most recent submission. If the poll does not allow resubmissions, this action will fail." + }, + { + "name": "status", + "args": [], + "returns": { + "type": "(bool,bool,(string,uint64)[])", + "desc": "A tuple containing the following information, in order: whether the poll allows resubmission, whether the poll is open, and an array of the poll's current results. This array contains one entry per option, and each entry is a tuple of that option's value and the number of accounts who have voted for it." + }, + "desc": "Get the status of this poll." + } + ], + "networks": {}, + "desc": "This is a polling application with no restrictions on who can participate." +} \ No newline at end of file diff --git a/examples/application/abi/poll/contract.py b/examples/application/abi/poll/contract.py new file mode 100644 index 000000000..258a3b855 --- /dev/null +++ b/examples/application/abi/poll/contract.py @@ -0,0 +1,107 @@ +# This example is provided for informational purposes only and has not been audited for security. +from pyteal import * +import json + + +def on_delete() -> Expr: + return Assert(Txn.sender() == Global.creator_address()) + + +router = Router( + name="OpenPollingApp", + descr="This is a polling application with no restrictions on who can participate.", + bare_calls=BareCallActions( + delete_application=OnCompleteAction.call_only(on_delete()) + ), +) + + +@router.method(no_op=CallConfig.CREATE) +def create(options: abi.DynamicArray[abi.String], can_resubmit: abi.Bool) -> Expr: + """Create a new polling application. + + Args: + options: A list of options for the poll. This list should not contain duplicate entries. + resubmission: A boolean value indicating whether this poll allows accounts to change their + answer after their first submission. + can_resubmit: Whether this poll allows accounts to change their submissions or not. + """ + return Seq() + + +@router.method(name="open") +def open_poll() -> Expr: + """Marks this poll as open. + + This will fail if the poll is already open. + + The poll must be open in order to receive user input. + """ + return Seq() + + +@router.method(name="close") +def close_poll() -> Expr: + """Marks this poll as closed. + + This will fail if the poll is already closed. + """ + return Seq() + + +@router.method +def submit(choice: abi.Uint16) -> Expr: + """Submit a response to the poll. + + Submissions can only be received if the poll is open. If the poll is closed, this will fail. + + If a submission has already been made by the sender and the poll allows resubmissions, the + sender's choice will be updated to the most recent submission. If the poll does not allow + resubmissions, this action will fail. + + Args: + choice: The choice made by the sender. This must be an index into the options for this poll. + """ + return Seq() + + +class PollStatus(abi.NamedTuple): + can_resubmit: abi.Field[abi.Bool] + is_open: abi.Field[abi.Bool] + results: abi.Field[abi.DynamicArray[abi.Tuple2[abi.String, abi.Uint64]]] + + +@router.method +def status(*, output: PollStatus) -> Expr: + """Get the status of this poll. + + Returns: + A tuple containing the following information, in order: whether the poll allows + resubmission, whether the poll is open, and an array of the poll's current results. This + array contains one entry per option, and each entry is a tuple of that option's value and + the number of accounts who have voted for it. + """ + can_resubmit = abi.make(abi.Bool) + is_open = abi.make(abi.Bool) + results = abi.make(abi.DynamicArray[abi.Tuple2[abi.String, abi.Uint64]]) + return Seq( + can_resubmit.set(False), + is_open.set(False), + results.set([]), + output.set(can_resubmit, is_open, results), + ) + + +approval_program, clear_state_program, contract = router.compile_program( + version=8, optimize=OptimizeOptions(scratch_slots=True) +) + +if __name__ == "__main__": + with open("approval.teal", "w") as f: + f.write(approval_program) + + with open("clear_state.teal", "w") as f: + f.write(clear_state_program) + + with open("contract.json", "w") as f: + f.write(json.dumps(contract.dictify(), indent=4)) diff --git a/examples/application/abi/vote/approval.teal b/examples/application/abi/vote/approval.teal deleted file mode 100644 index ba95445f2..000000000 --- a/examples/application/abi/vote/approval.teal +++ /dev/null @@ -1,3 +0,0 @@ -#pragma version 8 -int 0 -return \ No newline at end of file diff --git a/examples/application/abi/vote/contract.json b/examples/application/abi/vote/contract.json deleted file mode 100644 index 66052b39f..000000000 --- a/examples/application/abi/vote/contract.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Vote", - "methods": [], - "networks": {} -} \ No newline at end of file diff --git a/examples/application/abi/vote/contract.py b/examples/application/abi/vote/contract.py deleted file mode 100644 index 62df47fd9..000000000 --- a/examples/application/abi/vote/contract.py +++ /dev/null @@ -1,19 +0,0 @@ -# This example is provided for informational purposes only and has not been audited for security. -from pyteal import * -import json - -router = Router(name="Vote") - -approval_program, clear_state_program, contract = router.compile_program( - version=8, optimize=OptimizeOptions(scratch_slots=True) -) - -if __name__ == "__main__": - with open("approval.teal", "w") as f: - f.write(approval_program) - - with open("clear_state.teal", "w") as f: - f.write(clear_state_program) - - with open("contract.json", "w") as f: - f.write(json.dumps(contract.dictify(), indent=4)) From 6e856f4495eedcb108c845c6bd59836d19059ca3 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 7 Nov 2022 13:55:51 -0800 Subject: [PATCH 4/6] Update tests --- .flake8 | 2 +- tests/unit/compile_test.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.flake8 b/.flake8 index cde33a757..c968f1e00 100644 --- a/.flake8 +++ b/.flake8 @@ -11,7 +11,7 @@ ignore = per-file-ignores = pyteal/compiler/optimizer/__init__.py: F401 examples/application/abi/algobank/algobank.py: F403, F405 - examples/application/abi/vote/contract.py: F403, F405 + examples/application/abi/poll/contract.py: F403, F405 examples/application/asset.py: F403, F405 examples/application/opup.py: F403, F405 examples/application/security_token.py: F403, F405 diff --git a/tests/unit/compile_test.py b/tests/unit/compile_test.py index 842053992..90db67316 100644 --- a/tests/unit/compile_test.py +++ b/tests/unit/compile_test.py @@ -36,13 +36,13 @@ def test_abi_algobank(): def test_abi_vote(): - from examples.application.abi.vote.contract import ( + from examples.application.abi.poll.contract import ( approval_program, clear_state_program, contract, ) - target_dir = Path.cwd() / "examples" / "application" / "abi" / "vote" + target_dir = Path.cwd() / "examples" / "application" / "abi" / "poll" with open(target_dir / "approval.teal", "r") as expected_approval_program_file: expected_approval_program = "".join( From 968604e84034e96fefe578c7ffe0b5b0a74fff5a Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 15 Nov 2022 11:22:01 -0800 Subject: [PATCH 5/6] Implement contract --- examples/application/abi/poll/approval.teal | 310 +++++++++++++++++++- examples/application/abi/poll/contract.json | 8 +- examples/application/abi/poll/contract.py | 105 ++++++- 3 files changed, 390 insertions(+), 33 deletions(-) diff --git a/examples/application/abi/poll/approval.teal b/examples/application/abi/poll/approval.teal index ae45c7bdf..bcad59a35 100644 --- a/examples/application/abi/poll/approval.teal +++ b/examples/application/abi/poll/approval.teal @@ -4,7 +4,7 @@ int 0 == bnz main_l12 txna ApplicationArgs 0 -method "create(string[],bool)void" +method "create(string[3],bool)void" == bnz main_l11 txna ApplicationArgs 0 @@ -16,11 +16,11 @@ method "close()void" == bnz main_l9 txna ApplicationArgs 0 -method "submit(uint16)void" +method "submit(uint8)void" == bnz main_l8 txna ApplicationArgs 0 -method "status()(bool,bool,(string,uint64)[])" +method "status()(bool,bool,(string,uint64)[3])" == bnz main_l7 err @@ -52,7 +52,7 @@ int 0 assert txna ApplicationArgs 1 int 0 -extract_uint16 +getbyte callsub submit_3 int 1 return @@ -122,35 +122,317 @@ return // create create_0: -store 8 -store 7 +store 20 +store 19 +byte 0x6f70656e +int 0 +app_global_put +byte 0x72657375626d6974 +load 20 +app_global_put +byte 0x6f7074696f6e5f6e616d655f00 +load 19 +load 19 +int 2 +int 0 +* +extract_uint16 +int 0 +int 1 ++ +int 3 +== +bnz create_0_l8 +load 19 +int 2 +int 0 +* +int 2 ++ +extract_uint16 +create_0_l2: +substring3 +extract 2 0 +app_global_put +byte 0x6f7074696f6e5f636f756e745f00 +int 0 +app_global_put +byte 0x6f7074696f6e5f6e616d655f01 +load 19 +load 19 +int 2 +int 1 +* +extract_uint16 +int 1 +int 1 ++ +int 3 +== +bnz create_0_l7 +load 19 +int 2 +int 1 +* +int 2 ++ +extract_uint16 +create_0_l4: +substring3 +extract 2 0 +app_global_put +byte 0x6f7074696f6e5f636f756e745f01 +int 0 +app_global_put +byte 0x6f7074696f6e5f6e616d655f02 +load 19 +load 19 +int 2 +int 2 +* +extract_uint16 +int 2 +int 1 ++ +int 3 +== +bnz create_0_l6 +load 19 +int 2 +int 2 +* +int 2 ++ +extract_uint16 +b create_0_l9 +create_0_l6: +load 19 +len +b create_0_l9 +create_0_l7: +load 19 +len +b create_0_l4 +create_0_l8: +load 19 +len +b create_0_l2 +create_0_l9: +substring3 +extract 2 0 +app_global_put +byte 0x6f7074696f6e5f636f756e745f02 +int 0 +app_global_put retsub // open open_1: +byte 0x6f70656e +app_global_get +! +assert +byte 0x6f70656e +int 1 +app_global_put retsub // close close_2: +byte 0x6f70656e +app_global_get +assert +byte 0x6f70656e +int 0 +app_global_put retsub // submit submit_3: -store 9 +store 21 +load 21 +int 3 +< +assert +byte 0x6f7074696f6e5f636f756e745f00 +int 13 +load 21 +setbyte +store 22 +txn Sender +box_get +store 25 +store 24 +load 25 +bz submit_3_l2 +byte 0x72657375626d6974 +app_global_get +assert +byte 0x6f7074696f6e5f636f756e745f00 +int 13 +load 24 +btoi +setbyte +store 23 +load 23 +load 23 +app_global_get +int 1 +- +app_global_put +submit_3_l2: +txn Sender +byte 0x00 +int 0 +load 21 +setbyte +box_put +load 22 +load 22 +app_global_get +int 1 ++ +app_global_put retsub // status status_4: -int 0 +byte 0x72657375626d6974 +app_global_get +! +! store 3 -int 0 +byte 0x6f70656e +app_global_get +! +! store 4 -int 0 +byte 0x6f7074696f6e5f6e616d655f00 +app_global_get +store 5 +load 5 +len itob extract 6 0 -byte "" +load 5 concat store 5 +byte 0x6f7074696f6e5f636f756e745f00 +app_global_get +store 6 +load 5 +store 11 +int 10 +itob +extract 6 0 +load 6 +itob +concat +load 11 +concat +store 7 +byte 0x6f7074696f6e5f6e616d655f01 +app_global_get +store 5 +load 5 +len +itob +extract 6 0 +load 5 +concat +store 5 +byte 0x6f7074696f6e5f636f756e745f01 +app_global_get +store 6 +load 5 +store 12 +int 10 +itob +extract 6 0 +load 6 +itob +concat +load 12 +concat +store 8 +byte 0x6f7074696f6e5f6e616d655f02 +app_global_get +store 5 +load 5 +len +itob +extract 6 0 +load 5 +concat +store 5 +byte 0x6f7074696f6e5f636f756e745f02 +app_global_get +store 6 +load 5 +store 13 +int 10 +itob +extract 6 0 +load 6 +itob +concat +load 13 +concat +store 9 +load 7 +store 17 +load 17 +store 16 +int 6 +store 14 +load 14 +load 17 +len ++ +store 15 +load 15 +int 65536 +< +assert +load 14 +itob +extract 6 0 +load 8 +store 17 +load 16 +load 17 +concat +store 16 +load 15 +store 14 +load 14 +load 17 +len ++ +store 15 +load 15 +int 65536 +< +assert +load 14 +itob +extract 6 0 +concat +load 9 +store 17 +load 16 +load 17 +concat +store 16 +load 15 +store 14 +load 14 +itob +extract 6 0 +concat +load 16 +concat +store 10 byte 0x00 int 0 load 3 @@ -158,12 +440,12 @@ setbit int 1 load 4 setbit -load 5 -store 6 +load 10 +store 18 int 3 itob extract 6 0 concat -load 6 +load 18 concat retsub \ No newline at end of file diff --git a/examples/application/abi/poll/contract.json b/examples/application/abi/poll/contract.json index ea2717a94..159fa986c 100644 --- a/examples/application/abi/poll/contract.json +++ b/examples/application/abi/poll/contract.json @@ -5,7 +5,7 @@ "name": "create", "args": [ { - "type": "string[]", + "type": "string[3]", "name": "options", "desc": "A list of options for the poll. This list should not contain duplicate entries." }, @@ -40,7 +40,7 @@ "name": "submit", "args": [ { - "type": "uint16", + "type": "uint8", "name": "choice", "desc": "The choice made by the sender. This must be an index into the options for this poll." } @@ -54,12 +54,12 @@ "name": "status", "args": [], "returns": { - "type": "(bool,bool,(string,uint64)[])", + "type": "(bool,bool,(string,uint64)[3])", "desc": "A tuple containing the following information, in order: whether the poll allows resubmission, whether the poll is open, and an array of the poll's current results. This array contains one entry per option, and each entry is a tuple of that option's value and the number of accounts who have voted for it." }, "desc": "Get the status of this poll." } ], "networks": {}, - "desc": "This is a polling application with no restrictions on who can participate." + "desc": "A polling application with no restrictions on who can participate." } \ No newline at end of file diff --git a/examples/application/abi/poll/contract.py b/examples/application/abi/poll/contract.py index 258a3b855..1689065cc 100644 --- a/examples/application/abi/poll/contract.py +++ b/examples/application/abi/poll/contract.py @@ -1,6 +1,7 @@ # This example is provided for informational purposes only and has not been audited for security. -from pyteal import * import json +from typing import Literal +from pyteal import * def on_delete() -> Expr: @@ -9,24 +10,48 @@ def on_delete() -> Expr: router = Router( name="OpenPollingApp", - descr="This is a polling application with no restrictions on who can participate.", + descr="A polling application with no restrictions on who can participate.", bare_calls=BareCallActions( delete_application=OnCompleteAction.call_only(on_delete()) ), ) +open_key = Bytes(b"open") +resubmit_key = Bytes(b"resubmit") +option_name_prefix = b"option_name_" +option_name_keys = [ + Bytes(option_name_prefix + b"\x00"), + Bytes(option_name_prefix + b"\x01"), + Bytes(option_name_prefix + b"\x02"), +] +option_count_prefix = b"option_count_" +option_count_keys = [ + Bytes(option_count_prefix + b"\x00"), + Bytes(option_count_prefix + b"\x01"), + Bytes(option_count_prefix + b"\x02"), +] + @router.method(no_op=CallConfig.CREATE) -def create(options: abi.DynamicArray[abi.String], can_resubmit: abi.Bool) -> Expr: +def create( + options: abi.StaticArray[abi.String, Literal[3]], can_resubmit: abi.Bool +) -> Expr: """Create a new polling application. Args: options: A list of options for the poll. This list should not contain duplicate entries. - resubmission: A boolean value indicating whether this poll allows accounts to change their - answer after their first submission. can_resubmit: Whether this poll allows accounts to change their submissions or not. """ - return Seq() + return Seq( + App.globalPut(open_key, Int(0)), + App.globalPut(resubmit_key, can_resubmit.get()), + App.globalPut(option_name_keys[0], options[0].use(lambda value: value.get())), + App.globalPut(option_count_keys[0], Int(0)), + App.globalPut(option_name_keys[1], options[1].use(lambda value: value.get())), + App.globalPut(option_count_keys[1], Int(0)), + App.globalPut(option_name_keys[2], options[2].use(lambda value: value.get())), + App.globalPut(option_count_keys[2], Int(0)), + ) @router.method(name="open") @@ -37,7 +62,10 @@ def open_poll() -> Expr: The poll must be open in order to receive user input. """ - return Seq() + return Seq( + Assert(Not(App.globalGet(open_key))), + App.globalPut(open_key, Int(1)), + ) @router.method(name="close") @@ -46,11 +74,14 @@ def close_poll() -> Expr: This will fail if the poll is already closed. """ - return Seq() + return Seq( + Assert(App.globalGet(open_key)), + App.globalPut(open_key, Int(0)), + ) @router.method -def submit(choice: abi.Uint16) -> Expr: +def submit(choice: abi.Uint8) -> Expr: """Submit a response to the poll. Submissions can only be received if the poll is open. If the poll is closed, this will fail. @@ -62,13 +93,41 @@ def submit(choice: abi.Uint16) -> Expr: Args: choice: The choice made by the sender. This must be an index into the options for this poll. """ - return Seq() + new_choice_count_key = ScratchVar(TealType.bytes) + old_choice_count_key = ScratchVar(TealType.bytes) + return Seq( + Assert(choice.get() < Int(3)), + new_choice_count_key.store( + SetByte(option_count_keys[0], Int(len(option_count_prefix)), choice.get()) + ), + sender_box := App.box_get(Txn.sender()), + If(sender_box.hasValue()).Then( + # the sender has already submitted a response, so it must be cleared + Assert(App.globalGet(resubmit_key)), + old_choice_count_key.store( + SetByte( + option_count_keys[0], + Int(len(option_count_prefix)), + Btoi(sender_box.value()), + ) + ), + App.globalPut( + old_choice_count_key.load(), + App.globalGet(old_choice_count_key.load()) - Int(1), + ), + ), + App.box_put(Txn.sender(), choice.encode()), + App.globalPut( + new_choice_count_key.load(), + App.globalGet(new_choice_count_key.load()) + Int(1), + ), + ) class PollStatus(abi.NamedTuple): can_resubmit: abi.Field[abi.Bool] is_open: abi.Field[abi.Bool] - results: abi.Field[abi.DynamicArray[abi.Tuple2[abi.String, abi.Uint64]]] + results: abi.Field[abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[3]]] @router.method @@ -83,11 +142,27 @@ def status(*, output: PollStatus) -> Expr: """ can_resubmit = abi.make(abi.Bool) is_open = abi.make(abi.Bool) - results = abi.make(abi.DynamicArray[abi.Tuple2[abi.String, abi.Uint64]]) + option_name = abi.make(abi.String) + option_count = abi.make(abi.Uint64) + partial_results = [ + abi.make(abi.Tuple2[abi.String, abi.Uint64]), + abi.make(abi.Tuple2[abi.String, abi.Uint64]), + abi.make(abi.Tuple2[abi.String, abi.Uint64]), + ] + results = abi.make(abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[3]]) return Seq( - can_resubmit.set(False), - is_open.set(False), - results.set([]), + can_resubmit.set(App.globalGet(resubmit_key)), + is_open.set(App.globalGet(open_key)), + option_name.set(App.globalGet(option_name_keys[0])), + option_count.set(App.globalGet(option_count_keys[0])), + partial_results[0].set(option_name, option_count), + option_name.set(App.globalGet(option_name_keys[1])), + option_count.set(App.globalGet(option_count_keys[1])), + partial_results[1].set(option_name, option_count), + option_name.set(App.globalGet(option_name_keys[2])), + option_count.set(App.globalGet(option_count_keys[2])), + partial_results[2].set(option_name, option_count), + results.set([partial_results[0], partial_results[1], partial_results[2]]), output.set(can_resubmit, is_open, results), ) From adbe9a89f410a5561b1e77f4b94a18692a7b34ce Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 15 Nov 2022 11:22:49 -0800 Subject: [PATCH 6/6] Update tests/unit/compile_test.py Co-authored-by: Zeph Grunschlag --- tests/unit/compile_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/compile_test.py b/tests/unit/compile_test.py index 90db67316..ed608aac8 100644 --- a/tests/unit/compile_test.py +++ b/tests/unit/compile_test.py @@ -35,7 +35,7 @@ def test_abi_algobank(): assert contract.dictify() == expected_contract -def test_abi_vote(): +def test_abi_poll(): from examples.application.abi.poll.contract import ( approval_program, clear_state_program,