diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.approval.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.approval.teal new file mode 100644 index 0000000..f8302c1 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.approval.teal @@ -0,0 +1,111 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.ForLoopsExample.approval_program: + callsub __puya_arc4_router__ + return + + +// smart_contracts.control_flow.contract.ForLoopsExample.__puya_arc4_router__() -> uint64: +__puya_arc4_router__: + proto 0 1 + txn NumAppArgs + bz __puya_arc4_router___bare_routing@5 + method "for_loop()uint8[4]" + txna ApplicationArgs 0 + match __puya_arc4_router___for_loop_route@2 + int 0 + retsub + +__puya_arc4_router___for_loop_route@2: + txn OnCompletion + ! + assert // OnCompletion is NoOp + txn ApplicationID + assert // is not creating + callsub for_loop + byte 0x151f7c75 + swap + concat + log + int 1 + retsub + +__puya_arc4_router___bare_routing@5: + txn OnCompletion + bnz __puya_arc4_router___after_if_else@9 + txn ApplicationID + ! + assert // is creating + int 1 + retsub + +__puya_arc4_router___after_if_else@9: + int 0 + retsub + + +// smart_contracts.control_flow.contract.ForLoopsExample.for_loop() -> bytes: +for_loop: + proto 0 1 + byte "" + byte 0x00000000 + int 3 + int 0 + +for_loop_for_body@2: + frame_dig 2 + dup + itob + extract 7 1 + frame_dig 3 + dup + int 4 + < + assert // Index access is out of bounds + frame_dig 1 + swap + uncover 2 + replace3 + frame_bury 1 + bz for_loop_after_for@5 + frame_dig 2 + int 1 + - + frame_bury 2 + frame_dig 3 + int 1 + + + frame_bury 3 + b for_loop_for_body@2 + +for_loop_after_for@5: + int 0 + frame_bury 3 + +for_loop_for_header@6: + frame_dig 3 + int 4 + < + dup + frame_bury 0 + bz for_loop_after_for@9 + frame_dig 3 + dup + itob + extract 7 1 + frame_dig 0 + assert // Index access is out of bounds + frame_dig 1 + dig 2 + uncover 2 + replace3 + frame_bury 1 + int 1 + + + frame_bury 3 + b for_loop_for_header@6 + +for_loop_after_for@9: + frame_dig 1 + frame_bury 0 + retsub diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.arc32.json b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.arc32.json new file mode 100644 index 0000000..f562f01 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.arc32.json @@ -0,0 +1,50 @@ +{ + "hints": { + "for_loop()uint8[4]": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LkZvckxvb3BzRXhhbXBsZS5hcHByb3ZhbF9wcm9ncmFtOgogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5Gb3JMb29wc0V4YW1wbGUuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgcHJvdG8gMCAxCiAgICB0eG4gTnVtQXBwQXJncwogICAgYnogX19wdXlhX2FyYzRfcm91dGVyX19fYmFyZV9yb3V0aW5nQDUKICAgIG1ldGhvZCAiZm9yX2xvb3AoKXVpbnQ4WzRdIgogICAgdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAogICAgbWF0Y2ggX19wdXlhX2FyYzRfcm91dGVyX19fZm9yX2xvb3Bfcm91dGVAMgogICAgaW50IDAKICAgIHJldHN1YgoKX19wdXlhX2FyYzRfcm91dGVyX19fZm9yX2xvb3Bfcm91dGVAMjoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIGNhbGxzdWIgZm9yX2xvb3AKICAgIGJ5dGUgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1OgogICAgdHhuIE9uQ29tcGxldGlvbgogICAgYm56IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAOQogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgICEKICAgIGFzc2VydCAvLyBpcyBjcmVhdGluZwogICAgaW50IDEKICAgIHJldHN1YgoKX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5OgogICAgaW50IDAKICAgIHJldHN1YgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5jb250cm9sX2Zsb3cuY29udHJhY3QuRm9yTG9vcHNFeGFtcGxlLmZvcl9sb29wKCkgLT4gYnl0ZXM6CmZvcl9sb29wOgogICAgcHJvdG8gMCAxCiAgICBieXRlICIiCiAgICBieXRlIDB4MDAwMDAwMDAKICAgIGludCAzCiAgICBpbnQgMAoKZm9yX2xvb3BfZm9yX2JvZHlAMjoKICAgIGZyYW1lX2RpZyAyCiAgICBkdXAKICAgIGl0b2IKICAgIGV4dHJhY3QgNyAxCiAgICBmcmFtZV9kaWcgMwogICAgZHVwCiAgICBpbnQgNAogICAgPAogICAgYXNzZXJ0IC8vIEluZGV4IGFjY2VzcyBpcyBvdXQgb2YgYm91bmRzCiAgICBmcmFtZV9kaWcgMQogICAgc3dhcAogICAgdW5jb3ZlciAyCiAgICByZXBsYWNlMwogICAgZnJhbWVfYnVyeSAxCiAgICBieiBmb3JfbG9vcF9hZnRlcl9mb3JANQogICAgZnJhbWVfZGlnIDIKICAgIGludCAxCiAgICAtCiAgICBmcmFtZV9idXJ5IDIKICAgIGZyYW1lX2RpZyAzCiAgICBpbnQgMQogICAgKwogICAgZnJhbWVfYnVyeSAzCiAgICBiIGZvcl9sb29wX2Zvcl9ib2R5QDIKCmZvcl9sb29wX2FmdGVyX2ZvckA1OgogICAgaW50IDAKICAgIGZyYW1lX2J1cnkgMwoKZm9yX2xvb3BfZm9yX2hlYWRlckA2OgogICAgZnJhbWVfZGlnIDMKICAgIGludCA0CiAgICA8CiAgICBkdXAKICAgIGZyYW1lX2J1cnkgMAogICAgYnogZm9yX2xvb3BfYWZ0ZXJfZm9yQDkKICAgIGZyYW1lX2RpZyAzCiAgICBkdXAKICAgIGl0b2IKICAgIGV4dHJhY3QgNyAxCiAgICBmcmFtZV9kaWcgMAogICAgYXNzZXJ0IC8vIEluZGV4IGFjY2VzcyBpcyBvdXQgb2YgYm91bmRzCiAgICBmcmFtZV9kaWcgMQogICAgZGlnIDIKICAgIHVuY292ZXIgMgogICAgcmVwbGFjZTMKICAgIGZyYW1lX2J1cnkgMQogICAgaW50IDEKICAgICsKICAgIGZyYW1lX2J1cnkgMwogICAgYiBmb3JfbG9vcF9mb3JfaGVhZGVyQDYKCmZvcl9sb29wX2FmdGVyX2ZvckA5OgogICAgZnJhbWVfZGlnIDEKICAgIGZyYW1lX2J1cnkgMAogICAgcmV0c3ViCg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LkZvckxvb3BzRXhhbXBsZS5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgaW50IDEKICAgIHJldHVybgo=" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "ForLoopsExample", + "methods": [ + { + "name": "for_loop", + "args": [], + "readonly": false, + "returns": { + "type": "uint8[4]" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +} \ No newline at end of file diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.clear.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.clear.teal new file mode 100644 index 0000000..21011f6 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/ForLoopsExample.clear.teal @@ -0,0 +1,5 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.ForLoopsExample.clear_state_program: + int 1 + return diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.approval.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.approval.teal new file mode 100644 index 0000000..4a179c4 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.approval.teal @@ -0,0 +1,115 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.IfElseExample.approval_program: + callsub __puya_arc4_router__ + return + + +// smart_contracts.control_flow.contract.IfElseExample.__puya_arc4_router__() -> uint64: +__puya_arc4_router__: + proto 0 1 + txn NumAppArgs + bz __puya_arc4_router___bare_routing@6 + method "is_rich(uint64)string" + method "is_even(uint64)string" + txna ApplicationArgs 0 + match __puya_arc4_router___is_rich_route@2 __puya_arc4_router___is_even_route@3 + int 0 + retsub + +__puya_arc4_router___is_rich_route@2: + txn OnCompletion + ! + assert // OnCompletion is NoOp + txn ApplicationID + assert // is not creating + txna ApplicationArgs 1 + btoi + callsub is_rich + dup + len + itob + extract 6 2 + swap + concat + byte 0x151f7c75 + swap + concat + log + int 1 + retsub + +__puya_arc4_router___is_even_route@3: + txn OnCompletion + ! + assert // OnCompletion is NoOp + txn ApplicationID + assert // is not creating + txna ApplicationArgs 1 + btoi + callsub is_even + dup + len + itob + extract 6 2 + swap + concat + byte 0x151f7c75 + swap + concat + log + int 1 + retsub + +__puya_arc4_router___bare_routing@6: + txn OnCompletion + bnz __puya_arc4_router___after_if_else@10 + txn ApplicationID + ! + assert // is creating + int 1 + retsub + +__puya_arc4_router___after_if_else@10: + int 0 + retsub + + +// smart_contracts.control_flow.contract.IfElseExample.is_rich(account_balance: uint64) -> bytes: +is_rich: + proto 1 1 + frame_dig -1 + int 1000 + > + bz is_rich_else_body@2 + byte "This account is rich!" + retsub + +is_rich_else_body@2: + frame_dig -1 + int 100 + > + bz is_rich_else_body@4 + byte "This account is doing well." + retsub + +is_rich_else_body@4: + byte "This account is poor :(" + retsub + + +// smart_contracts.control_flow.contract.IfElseExample.is_even(number: uint64) -> bytes: +is_even: + proto 1 1 + frame_dig -1 + int 2 + % + bnz is_even_ternary_false@2 + byte "Even" + b is_even_ternary_merge@3 + +is_even_ternary_false@2: + byte "Odd" + +is_even_ternary_merge@3: + retsub diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.arc32.json b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.arc32.json new file mode 100644 index 0000000..72cffa5 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.arc32.json @@ -0,0 +1,73 @@ +{ + "hints": { + "is_rich(uint64)string": { + "call_config": { + "no_op": "CALL" + } + }, + "is_even(uint64)string": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LklmRWxzZUV4YW1wbGUuYXBwcm92YWxfcHJvZ3JhbToKICAgIGNhbGxzdWIgX19wdXlhX2FyYzRfcm91dGVyX18KICAgIHJldHVybgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5jb250cm9sX2Zsb3cuY29udHJhY3QuSWZFbHNlRXhhbXBsZS5fX3B1eWFfYXJjNF9yb3V0ZXJfXygpIC0+IHVpbnQ2NDoKX19wdXlhX2FyYzRfcm91dGVyX186CiAgICBwcm90byAwIDEKICAgIHR4biBOdW1BcHBBcmdzCiAgICBieiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANgogICAgbWV0aG9kICJpc19yaWNoKHVpbnQ2NClzdHJpbmciCiAgICBtZXRob2QgImlzX2V2ZW4odWludDY0KXN0cmluZyIKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDAKICAgIG1hdGNoIF9fcHV5YV9hcmM0X3JvdXRlcl9fX2lzX3JpY2hfcm91dGVAMiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19pc19ldmVuX3JvdXRlQDMKICAgIGludCAwCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2lzX3JpY2hfcm91dGVAMjoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGJ0b2kKICAgIGNhbGxzdWIgaXNfcmljaAogICAgZHVwCiAgICBsZW4KICAgIGl0b2IKICAgIGV4dHJhY3QgNiAyCiAgICBzd2FwCiAgICBjb25jYXQKICAgIGJ5dGUgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2lzX2V2ZW5fcm91dGVAMzoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGJ0b2kKICAgIGNhbGxzdWIgaXNfZXZlbgogICAgZHVwCiAgICBsZW4KICAgIGl0b2IKICAgIGV4dHJhY3QgNiAyCiAgICBzd2FwCiAgICBjb25jYXQKICAgIGJ5dGUgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A2OgogICAgdHhuIE9uQ29tcGxldGlvbgogICAgYm56IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAMTAKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gaXMgY3JlYXRpbmcKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAMTA6CiAgICBpbnQgMAogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5JZkVsc2VFeGFtcGxlLmlzX3JpY2goYWNjb3VudF9iYWxhbmNlOiB1aW50NjQpIC0+IGJ5dGVzOgppc19yaWNoOgogICAgcHJvdG8gMSAxCiAgICBmcmFtZV9kaWcgLTEKICAgIGludCAxMDAwCiAgICA+CiAgICBieiBpc19yaWNoX2Vsc2VfYm9keUAyCiAgICBieXRlICJUaGlzIGFjY291bnQgaXMgcmljaCEiCiAgICByZXRzdWIKCmlzX3JpY2hfZWxzZV9ib2R5QDI6CiAgICBmcmFtZV9kaWcgLTEKICAgIGludCAxMDAKICAgID4KICAgIGJ6IGlzX3JpY2hfZWxzZV9ib2R5QDQKICAgIGJ5dGUgIlRoaXMgYWNjb3VudCBpcyBkb2luZyB3ZWxsLiIKICAgIHJldHN1YgoKaXNfcmljaF9lbHNlX2JvZHlANDoKICAgIGJ5dGUgIlRoaXMgYWNjb3VudCBpcyBwb29yIDooIgogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5JZkVsc2VFeGFtcGxlLmlzX2V2ZW4obnVtYmVyOiB1aW50NjQpIC0+IGJ5dGVzOgppc19ldmVuOgogICAgcHJvdG8gMSAxCiAgICBmcmFtZV9kaWcgLTEKICAgIGludCAyCiAgICAlCiAgICBibnogaXNfZXZlbl90ZXJuYXJ5X2ZhbHNlQDIKICAgIGJ5dGUgIkV2ZW4iCiAgICBiIGlzX2V2ZW5fdGVybmFyeV9tZXJnZUAzCgppc19ldmVuX3Rlcm5hcnlfZmFsc2VAMjoKICAgIGJ5dGUgIk9kZCIKCmlzX2V2ZW5fdGVybmFyeV9tZXJnZUAzOgogICAgcmV0c3ViCg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LklmRWxzZUV4YW1wbGUuY2xlYXJfc3RhdGVfcHJvZ3JhbToKICAgIGludCAxCiAgICByZXR1cm4K" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "IfElseExample", + "methods": [ + { + "name": "is_rich", + "args": [ + { + "type": "uint64", + "name": "account_balance" + } + ], + "readonly": false, + "returns": { + "type": "string" + } + }, + { + "name": "is_even", + "args": [ + { + "type": "uint64", + "name": "number" + } + ], + "readonly": false, + "returns": { + "type": "string" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +} \ No newline at end of file diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.clear.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.clear.teal new file mode 100644 index 0000000..0388b39 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/IfElseExample.clear.teal @@ -0,0 +1,5 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.IfElseExample.clear_state_program: + int 1 + return diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.approval.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.approval.teal new file mode 100644 index 0000000..4612df8 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.approval.teal @@ -0,0 +1,89 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.MatchStatements.approval_program: + callsub __puya_arc4_router__ + return + + +// smart_contracts.control_flow.contract.MatchStatements.__puya_arc4_router__() -> uint64: +__puya_arc4_router__: + proto 0 1 + txn NumAppArgs + bz __puya_arc4_router___bare_routing@5 + method "get_day(uint64)string" + txna ApplicationArgs 0 + match __puya_arc4_router___get_day_route@2 + int 0 + retsub + +__puya_arc4_router___get_day_route@2: + txn OnCompletion + ! + assert // OnCompletion is NoOp + txn ApplicationID + assert // is not creating + txna ApplicationArgs 1 + btoi + callsub get_day + dup + len + itob + extract 6 2 + swap + concat + byte 0x151f7c75 + swap + concat + log + int 1 + retsub + +__puya_arc4_router___bare_routing@5: + txn OnCompletion + bnz __puya_arc4_router___after_if_else@9 + txn ApplicationID + ! + assert // is creating + int 1 + retsub + +__puya_arc4_router___after_if_else@9: + int 0 + retsub + + +// smart_contracts.control_flow.contract.MatchStatements.get_day(date: uint64) -> bytes: +get_day: + proto 1 1 + frame_dig -1 + switch get_day_switch_case_0@1 get_day_switch_case_1@2 get_day_switch_case_2@3 get_day_switch_case_3@4 get_day_switch_case_4@5 get_day_switch_case_5@6 get_day_switch_case_6@7 + byte "Invalid day" + retsub + +get_day_switch_case_0@1: + byte "Monday" + retsub + +get_day_switch_case_1@2: + byte "Tuesday" + retsub + +get_day_switch_case_2@3: + byte "Wednesday" + retsub + +get_day_switch_case_3@4: + byte "Thursday" + retsub + +get_day_switch_case_4@5: + byte "Friday" + retsub + +get_day_switch_case_5@6: + byte "Saturday" + retsub + +get_day_switch_case_6@7: + byte "Sunday" + retsub diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.arc32.json b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.arc32.json new file mode 100644 index 0000000..2ca34be --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.arc32.json @@ -0,0 +1,55 @@ +{ + "hints": { + "get_day(uint64)string": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0Lk1hdGNoU3RhdGVtZW50cy5hcHByb3ZhbF9wcm9ncmFtOgogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5NYXRjaFN0YXRlbWVudHMuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgcHJvdG8gMCAxCiAgICB0eG4gTnVtQXBwQXJncwogICAgYnogX19wdXlhX2FyYzRfcm91dGVyX19fYmFyZV9yb3V0aW5nQDUKICAgIG1ldGhvZCAiZ2V0X2RheSh1aW50NjQpc3RyaW5nIgogICAgdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAogICAgbWF0Y2ggX19wdXlhX2FyYzRfcm91dGVyX19fZ2V0X2RheV9yb3V0ZUAyCiAgICBpbnQgMAogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19nZXRfZGF5X3JvdXRlQDI6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICAhCiAgICBhc3NlcnQgLy8gT25Db21wbGV0aW9uIGlzIE5vT3AKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICBhc3NlcnQgLy8gaXMgbm90IGNyZWF0aW5nCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAxCiAgICBidG9pCiAgICBjYWxsc3ViIGdldF9kYXkKICAgIGR1cAogICAgbGVuCiAgICBpdG9iCiAgICBleHRyYWN0IDYgMgogICAgc3dhcAogICAgY29uY2F0CiAgICBieXRlIDB4MTUxZjdjNzUKICAgIHN3YXAKICAgIGNvbmNhdAogICAgbG9nCiAgICBpbnQgMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANToKICAgIHR4biBPbkNvbXBsZXRpb24KICAgIGJueiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDkKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gaXMgY3JlYXRpbmcKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAOToKICAgIGludCAwCiAgICByZXRzdWIKCgovLyBzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0Lk1hdGNoU3RhdGVtZW50cy5nZXRfZGF5KGRhdGU6IHVpbnQ2NCkgLT4gYnl0ZXM6CmdldF9kYXk6CiAgICBwcm90byAxIDEKICAgIGZyYW1lX2RpZyAtMQogICAgc3dpdGNoIGdldF9kYXlfc3dpdGNoX2Nhc2VfMEAxIGdldF9kYXlfc3dpdGNoX2Nhc2VfMUAyIGdldF9kYXlfc3dpdGNoX2Nhc2VfMkAzIGdldF9kYXlfc3dpdGNoX2Nhc2VfM0A0IGdldF9kYXlfc3dpdGNoX2Nhc2VfNEA1IGdldF9kYXlfc3dpdGNoX2Nhc2VfNUA2IGdldF9kYXlfc3dpdGNoX2Nhc2VfNkA3CiAgICBieXRlICJJbnZhbGlkIGRheSIKICAgIHJldHN1YgoKZ2V0X2RheV9zd2l0Y2hfY2FzZV8wQDE6CiAgICBieXRlICJNb25kYXkiCiAgICByZXRzdWIKCmdldF9kYXlfc3dpdGNoX2Nhc2VfMUAyOgogICAgYnl0ZSAiVHVlc2RheSIKICAgIHJldHN1YgoKZ2V0X2RheV9zd2l0Y2hfY2FzZV8yQDM6CiAgICBieXRlICJXZWRuZXNkYXkiCiAgICByZXRzdWIKCmdldF9kYXlfc3dpdGNoX2Nhc2VfM0A0OgogICAgYnl0ZSAiVGh1cnNkYXkiCiAgICByZXRzdWIKCmdldF9kYXlfc3dpdGNoX2Nhc2VfNEA1OgogICAgYnl0ZSAiRnJpZGF5IgogICAgcmV0c3ViCgpnZXRfZGF5X3N3aXRjaF9jYXNlXzVANjoKICAgIGJ5dGUgIlNhdHVyZGF5IgogICAgcmV0c3ViCgpnZXRfZGF5X3N3aXRjaF9jYXNlXzZANzoKICAgIGJ5dGUgIlN1bmRheSIKICAgIHJldHN1Ygo=", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0Lk1hdGNoU3RhdGVtZW50cy5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgaW50IDEKICAgIHJldHVybgo=" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "MatchStatements", + "methods": [ + { + "name": "get_day", + "args": [ + { + "type": "uint64", + "name": "date" + } + ], + "readonly": false, + "returns": { + "type": "string" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +} \ No newline at end of file diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.clear.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.clear.teal new file mode 100644 index 0000000..6728117 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/MatchStatements.clear.teal @@ -0,0 +1,5 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.MatchStatements.clear_state_program: + int 1 + return diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.approval.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.approval.teal new file mode 100644 index 0000000..979cac6 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.approval.teal @@ -0,0 +1,91 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.WhileLoopExample.approval_program: + callsub __puya_arc4_router__ + return + + +// smart_contracts.control_flow.contract.WhileLoopExample.__puya_arc4_router__() -> uint64: +__puya_arc4_router__: + proto 0 1 + txn NumAppArgs + bz __puya_arc4_router___bare_routing@5 + method "loop()uint64" + txna ApplicationArgs 0 + match __puya_arc4_router___loop_route@2 + int 0 + retsub + +__puya_arc4_router___loop_route@2: + txn OnCompletion + ! + assert // OnCompletion is NoOp + txn ApplicationID + assert // is not creating + callsub loop + itob + byte 0x151f7c75 + swap + concat + log + int 1 + retsub + +__puya_arc4_router___bare_routing@5: + txn OnCompletion + bnz __puya_arc4_router___after_if_else@9 + txn ApplicationID + ! + assert // is creating + int 1 + retsub + +__puya_arc4_router___after_if_else@9: + int 0 + retsub + + +// smart_contracts.control_flow.contract.WhileLoopExample.loop() -> uint64: +loop: + proto 0 1 + byte "" + int 10 + int 0 + +loop_while_top@1: + frame_dig 2 + frame_bury 0 + frame_dig 1 + bz loop_after_while@7 + frame_dig 1 + int 5 + > + bz loop_after_if_else@4 + frame_dig 1 + int 1 + - + frame_bury 1 + frame_dig 2 + int 1 + + + frame_bury 2 + b loop_while_top@1 + +loop_after_if_else@4: + frame_dig 1 + int 2 + - + dup + frame_bury 1 + frame_dig 2 + int 1 + + + frame_bury 2 + int 1 + == + bz loop_while_top@1 + frame_dig 2 + frame_bury 0 + +loop_after_while@7: + retsub diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.arc32.json b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.arc32.json new file mode 100644 index 0000000..e91dd5a --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.arc32.json @@ -0,0 +1,50 @@ +{ + "hints": { + "loop()uint64": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LldoaWxlTG9vcEV4YW1wbGUuYXBwcm92YWxfcHJvZ3JhbToKICAgIGNhbGxzdWIgX19wdXlhX2FyYzRfcm91dGVyX18KICAgIHJldHVybgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5jb250cm9sX2Zsb3cuY29udHJhY3QuV2hpbGVMb29wRXhhbXBsZS5fX3B1eWFfYXJjNF9yb3V0ZXJfXygpIC0+IHVpbnQ2NDoKX19wdXlhX2FyYzRfcm91dGVyX186CiAgICBwcm90byAwIDEKICAgIHR4biBOdW1BcHBBcmdzCiAgICBieiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANQogICAgbWV0aG9kICJsb29wKCl1aW50NjQiCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBfX3B1eWFfYXJjNF9yb3V0ZXJfX19sb29wX3JvdXRlQDIKICAgIGludCAwCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2xvb3Bfcm91dGVAMjoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIGNhbGxzdWIgbG9vcAogICAgaXRvYgogICAgYnl0ZSAweDE1MWY3Yzc1CiAgICBzd2FwCiAgICBjb25jYXQKICAgIGxvZwogICAgaW50IDEKICAgIHJldHN1YgoKX19wdXlhX2FyYzRfcm91dGVyX19fYmFyZV9yb3V0aW5nQDU6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBibnogX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgIQogICAgYXNzZXJ0IC8vIGlzIGNyZWF0aW5nCiAgICBpbnQgMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDk6CiAgICBpbnQgMAogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5XaGlsZUxvb3BFeGFtcGxlLmxvb3AoKSAtPiB1aW50NjQ6Cmxvb3A6CiAgICBwcm90byAwIDEKICAgIGJ5dGUgIiIKICAgIGludCAxMAogICAgaW50IDAKCmxvb3Bfd2hpbGVfdG9wQDE6CiAgICBmcmFtZV9kaWcgMgogICAgZnJhbWVfYnVyeSAwCiAgICBmcmFtZV9kaWcgMQogICAgYnogbG9vcF9hZnRlcl93aGlsZUA3CiAgICBmcmFtZV9kaWcgMQogICAgaW50IDUKICAgID4KICAgIGJ6IGxvb3BfYWZ0ZXJfaWZfZWxzZUA0CiAgICBmcmFtZV9kaWcgMQogICAgaW50IDEKICAgIC0KICAgIGZyYW1lX2J1cnkgMQogICAgZnJhbWVfZGlnIDIKICAgIGludCAxCiAgICArCiAgICBmcmFtZV9idXJ5IDIKICAgIGIgbG9vcF93aGlsZV90b3BAMQoKbG9vcF9hZnRlcl9pZl9lbHNlQDQ6CiAgICBmcmFtZV9kaWcgMQogICAgaW50IDIKICAgIC0KICAgIGR1cAogICAgZnJhbWVfYnVyeSAxCiAgICBmcmFtZV9kaWcgMgogICAgaW50IDEKICAgICsKICAgIGZyYW1lX2J1cnkgMgogICAgaW50IDEKICAgID09CiAgICBieiBsb29wX3doaWxlX3RvcEAxCiAgICBmcmFtZV9kaWcgMgogICAgZnJhbWVfYnVyeSAwCgpsb29wX2FmdGVyX3doaWxlQDc6CiAgICByZXRzdWIK", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LldoaWxlTG9vcEV4YW1wbGUuY2xlYXJfc3RhdGVfcHJvZ3JhbToKICAgIGludCAxCiAgICByZXR1cm4K" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "WhileLoopExample", + "methods": [ + { + "name": "loop", + "args": [], + "readonly": false, + "returns": { + "type": "uint64" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +} \ No newline at end of file diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.clear.teal b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.clear.teal new file mode 100644 index 0000000..0eec1ae --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/WhileLoopExample.clear.teal @@ -0,0 +1,5 @@ +#pragma version 10 + +smart_contracts.control_flow.contract.WhileLoopExample.clear_state_program: + int 1 + return diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/for_loops_example_client.py b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/for_loops_example_client.py new file mode 100644 index 0000000..9cc2d97 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/for_loops_example_client.py @@ -0,0 +1,477 @@ +# flake8: noqa +# fmt: off +# mypy: disable-error-code="no-any-return, no-untyped-call, misc, type-arg" +# This file was automatically generated by algokit-client-generator. +# DO NOT MODIFY IT BY HAND. +# requires: algokit-utils@^1.2.0 +import base64 +import dataclasses +import decimal +import typing +from abc import ABC, abstractmethod + +import algokit_utils +import algosdk +from algosdk.v2client import models +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AtomicTransactionResponse, + SimulateAtomicTransactionResponse, + TransactionSigner, + TransactionWithSigner +) + +_APP_SPEC_JSON = r"""{ + "hints": { + "for_loop()uint8[4]": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LkZvckxvb3BzRXhhbXBsZS5hcHByb3ZhbF9wcm9ncmFtOgogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5Gb3JMb29wc0V4YW1wbGUuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgcHJvdG8gMCAxCiAgICB0eG4gTnVtQXBwQXJncwogICAgYnogX19wdXlhX2FyYzRfcm91dGVyX19fYmFyZV9yb3V0aW5nQDUKICAgIG1ldGhvZCAiZm9yX2xvb3AoKXVpbnQ4WzRdIgogICAgdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAogICAgbWF0Y2ggX19wdXlhX2FyYzRfcm91dGVyX19fZm9yX2xvb3Bfcm91dGVAMgogICAgaW50IDAKICAgIHJldHN1YgoKX19wdXlhX2FyYzRfcm91dGVyX19fZm9yX2xvb3Bfcm91dGVAMjoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIGNhbGxzdWIgZm9yX2xvb3AKICAgIGJ5dGUgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1OgogICAgdHhuIE9uQ29tcGxldGlvbgogICAgYm56IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAOQogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgICEKICAgIGFzc2VydCAvLyBpcyBjcmVhdGluZwogICAgaW50IDEKICAgIHJldHN1YgoKX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5OgogICAgaW50IDAKICAgIHJldHN1YgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5jb250cm9sX2Zsb3cuY29udHJhY3QuRm9yTG9vcHNFeGFtcGxlLmZvcl9sb29wKCkgLT4gYnl0ZXM6CmZvcl9sb29wOgogICAgcHJvdG8gMCAxCiAgICBieXRlICIiCiAgICBieXRlIDB4MDAwMDAwMDAKICAgIGludCAzCiAgICBpbnQgMAoKZm9yX2xvb3BfZm9yX2JvZHlAMjoKICAgIGZyYW1lX2RpZyAyCiAgICBkdXAKICAgIGl0b2IKICAgIGV4dHJhY3QgNyAxCiAgICBmcmFtZV9kaWcgMwogICAgZHVwCiAgICBpbnQgNAogICAgPAogICAgYXNzZXJ0IC8vIEluZGV4IGFjY2VzcyBpcyBvdXQgb2YgYm91bmRzCiAgICBmcmFtZV9kaWcgMQogICAgc3dhcAogICAgdW5jb3ZlciAyCiAgICByZXBsYWNlMwogICAgZnJhbWVfYnVyeSAxCiAgICBieiBmb3JfbG9vcF9hZnRlcl9mb3JANQogICAgZnJhbWVfZGlnIDIKICAgIGludCAxCiAgICAtCiAgICBmcmFtZV9idXJ5IDIKICAgIGZyYW1lX2RpZyAzCiAgICBpbnQgMQogICAgKwogICAgZnJhbWVfYnVyeSAzCiAgICBiIGZvcl9sb29wX2Zvcl9ib2R5QDIKCmZvcl9sb29wX2FmdGVyX2ZvckA1OgogICAgaW50IDAKICAgIGZyYW1lX2J1cnkgMwoKZm9yX2xvb3BfZm9yX2hlYWRlckA2OgogICAgZnJhbWVfZGlnIDMKICAgIGludCA0CiAgICA8CiAgICBkdXAKICAgIGZyYW1lX2J1cnkgMAogICAgYnogZm9yX2xvb3BfYWZ0ZXJfZm9yQDkKICAgIGZyYW1lX2RpZyAzCiAgICBkdXAKICAgIGl0b2IKICAgIGV4dHJhY3QgNyAxCiAgICBmcmFtZV9kaWcgMAogICAgYXNzZXJ0IC8vIEluZGV4IGFjY2VzcyBpcyBvdXQgb2YgYm91bmRzCiAgICBmcmFtZV9kaWcgMQogICAgZGlnIDIKICAgIHVuY292ZXIgMgogICAgcmVwbGFjZTMKICAgIGZyYW1lX2J1cnkgMQogICAgaW50IDEKICAgICsKICAgIGZyYW1lX2J1cnkgMwogICAgYiBmb3JfbG9vcF9mb3JfaGVhZGVyQDYKCmZvcl9sb29wX2FmdGVyX2ZvckA5OgogICAgZnJhbWVfZGlnIDEKICAgIGZyYW1lX2J1cnkgMAogICAgcmV0c3ViCg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LkZvckxvb3BzRXhhbXBsZS5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgaW50IDEKICAgIHJldHVybgo=" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "ForLoopsExample", + "methods": [ + { + "name": "for_loop", + "args": [], + "returns": { + "type": "uint8[4]" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +}""" +APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) +_TReturn = typing.TypeVar("_TReturn") + + +class _ArgsBase(ABC, typing.Generic[_TReturn]): + @staticmethod + @abstractmethod + def method() -> str: + ... + + +_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) + + +@dataclasses.dataclass(kw_only=True) +class _TArgsHolder(typing.Generic[_TArgs]): + args: _TArgs + + +def _filter_none(value: dict | typing.Any) -> dict | typing.Any: + if isinstance(value, dict): + return {k: _filter_none(v) for k, v in value.items() if v is not None} + return value + + +def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: + if data is None: + return {} + if not dataclasses.is_dataclass(data): + raise TypeError(f"{data} must be a dataclass") + if convert_all: + result = dataclasses.asdict(data) # type: ignore[call-overload] + else: + result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} + return _filter_none(result) + + +def _convert_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.TransactionParametersDict: + return typing.cast(algokit_utils.TransactionParametersDict, _as_dict(transaction_parameters)) + + +def _convert_call_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.OnCompleteCallParametersDict: + return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_create_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, + on_complete: algokit_utils.OnCompleteActionName, +) -> algokit_utils.CreateCallParametersDict: + result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) + on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" + result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) + return result + + +def _convert_deploy_args( + deploy_args: algokit_utils.DeployCallArgs | None, +) -> algokit_utils.ABICreateCallArgsDict | None: + if deploy_args is None: + return None + + deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) + if isinstance(deploy_args, _TArgsHolder): + deploy_args_dict["args"] = _as_dict(deploy_args.args) + deploy_args_dict["method"] = deploy_args.args.method() + + return deploy_args_dict + + +@dataclasses.dataclass(kw_only=True) +class ForLoopArgs(_ArgsBase[list[int] | tuple[int, int, int, int]]): + @staticmethod + def method() -> str: + return "for_loop()uint8[4]" + + +@dataclasses.dataclass(kw_only=True) +class SimulateOptions: + allow_more_logs: bool = dataclasses.field(default=False) + allow_empty_signatures: bool = dataclasses.field(default=False) + extra_opcode_budget: int = dataclasses.field(default=0) + exec_trace_config: models.SimulateTraceConfig | None = dataclasses.field(default=None) + + +class Composer: + + def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): + self.app_client = app_client + self.atc = atc + + def build(self) -> AtomicTransactionComposer: + return self.atc + + def simulate(self, options: SimulateOptions | None = None) -> SimulateAtomicTransactionResponse: + request = models.SimulateRequest( + allow_more_logs=options.allow_more_logs, + allow_empty_signatures=options.allow_empty_signatures, + extra_opcode_budget=options.extra_opcode_budget, + exec_trace_config=options.exec_trace_config, + txn_groups=[] + ) if options else None + result = self.atc.simulate(self.app_client.algod_client, request) + return result + + def execute(self) -> AtomicTransactionResponse: + return self.app_client.execute_atc(self.atc) + + def for_loop( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `for_loop()uint8[4]` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = ForLoopArgs() + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> "Composer": + """Adds a call to create an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_create( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return self + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> "Composer": + """Adds a call to the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass""" + + self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) + return self + + +class ForLoopsExampleClient: + """A class for interacting with the ForLoopsExample app providing high productivity and + strongly typed methods to deploy and call the app""" + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account | None = None, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + """ + ForLoopsExampleClient can be created with an app_id to interact with an existing application, alternatively + it can be created with a creator and indexer_client specified to find existing applications by name and creator. + + :param AlgodClient algod_client: AlgoSDK algod client + :param int app_id: The app_id of an existing application, to instead find the application by creator and name + use the creator and indexer_client parameters + :param str | Account creator: The address or Account of the app creator to resolve the app_id + :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by + creator and app name + :param AppLookup existing_deployments: + :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and + creator was passed as an Account will use that. + :param str sender: Address to use as the sender for all transactions, will use the address associated with the + signer if not specified. + :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should + *NOT* include the TMPL_ prefix + :param str | None app_name: Name of application to use when deploying, defaults to name defined on the + Application Specification + """ + + self.app_spec = APP_SPEC + + # calling full __init__ signature, so ignoring mypy warning about overloads + self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] + algod_client=algod_client, + app_spec=self.app_spec, + app_id=app_id, + creator=creator, + indexer_client=indexer_client, + existing_deployments=existing_deployments, + signer=signer, + sender=sender, + suggested_params=suggested_params, + template_values=template_values, + app_name=app_name, + ) + + @property + def algod_client(self) -> algosdk.v2client.algod.AlgodClient: + return self.app_client.algod_client + + @property + def app_id(self) -> int: + return self.app_client.app_id + + @app_id.setter + def app_id(self, value: int) -> None: + self.app_client.app_id = value + + @property + def app_address(self) -> str: + return self.app_client.app_address + + @property + def sender(self) -> str | None: + return self.app_client.sender + + @sender.setter + def sender(self, value: str) -> None: + self.app_client.sender = value + + @property + def signer(self) -> TransactionSigner | None: + return self.app_client.signer + + @signer.setter + def signer(self, value: TransactionSigner) -> None: + self.app_client.signer = value + + @property + def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: + return self.app_client.suggested_params + + @suggested_params.setter + def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: + self.app_client.suggested_params = value + + def for_loop( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[list[int] | tuple[int, int, int, int]]: + """Calls `for_loop()uint8[4]` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[list[int] | tuple[int, int, int, int]]: The result of the transaction""" + + args = ForLoopArgs() + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Creates an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.create( + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return result + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) + + def deploy( + self, + version: str | None = None, + *, + signer: TransactionSigner | None = None, + sender: str | None = None, + allow_update: bool | None = None, + allow_delete: bool | None = None, + on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, + on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, + template_values: algokit_utils.TemplateValueMapping | None = None, + create_args: algokit_utils.DeployCallArgs | None = None, + update_args: algokit_utils.DeployCallArgs | None = None, + delete_args: algokit_utils.DeployCallArgs | None = None, + ) -> algokit_utils.DeployResponse: + """Deploy an application and update client to reference it. + + Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator + account, including deploy-time template placeholder substitutions. + To understand the architecture decisions behind this functionality please see + + + ```{note} + If there is a breaking state schema change to an existing app (and `on_schema_break` is set to + 'ReplaceApp' the existing app will be deleted and re-created. + ``` + + ```{note} + If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') + the existing app will be deleted and re-created. + ``` + + :param str version: version to use when creating or updating app, if None version will be auto incremented + :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app + , if None uses self.signer + :param str sender: sender address to use when deploying app, if None uses self.sender + :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app + can be deleted + :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app + can be updated + :param OnUpdate on_update: Determines what action to take if an application update is required + :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements + has increased beyond the current allocation + :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys + should *NOT* include the TMPL_ prefix + :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application + :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application + :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application + :return DeployResponse: details action taken and relevant transactions + :raises DeploymentError: If the deployment failed""" + + return self.app_client.deploy( + version, + signer=signer, + sender=sender, + allow_update=allow_update, + allow_delete=allow_delete, + on_update=on_update, + on_schema_break=on_schema_break, + template_values=template_values, + create_args=_convert_deploy_args(create_args), + update_args=_convert_deploy_args(update_args), + delete_args=_convert_deploy_args(delete_args), + ) + + def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: + return Composer(self.app_client, atc or AtomicTransactionComposer()) diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/if_else_example_client.py b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/if_else_example_client.py new file mode 100644 index 0000000..d42abd6 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/if_else_example_client.py @@ -0,0 +1,563 @@ +# flake8: noqa +# fmt: off +# mypy: disable-error-code="no-any-return, no-untyped-call, misc, type-arg" +# This file was automatically generated by algokit-client-generator. +# DO NOT MODIFY IT BY HAND. +# requires: algokit-utils@^1.2.0 +import base64 +import dataclasses +import decimal +import typing +from abc import ABC, abstractmethod + +import algokit_utils +import algosdk +from algosdk.v2client import models +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AtomicTransactionResponse, + SimulateAtomicTransactionResponse, + TransactionSigner, + TransactionWithSigner +) + +_APP_SPEC_JSON = r"""{ + "hints": { + "is_rich(uint64)string": { + "call_config": { + "no_op": "CALL" + } + }, + "is_even(uint64)string": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LklmRWxzZUV4YW1wbGUuYXBwcm92YWxfcHJvZ3JhbToKICAgIGNhbGxzdWIgX19wdXlhX2FyYzRfcm91dGVyX18KICAgIHJldHVybgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5jb250cm9sX2Zsb3cuY29udHJhY3QuSWZFbHNlRXhhbXBsZS5fX3B1eWFfYXJjNF9yb3V0ZXJfXygpIC0+IHVpbnQ2NDoKX19wdXlhX2FyYzRfcm91dGVyX186CiAgICBwcm90byAwIDEKICAgIHR4biBOdW1BcHBBcmdzCiAgICBieiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANgogICAgbWV0aG9kICJpc19yaWNoKHVpbnQ2NClzdHJpbmciCiAgICBtZXRob2QgImlzX2V2ZW4odWludDY0KXN0cmluZyIKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDAKICAgIG1hdGNoIF9fcHV5YV9hcmM0X3JvdXRlcl9fX2lzX3JpY2hfcm91dGVAMiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19pc19ldmVuX3JvdXRlQDMKICAgIGludCAwCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2lzX3JpY2hfcm91dGVAMjoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGJ0b2kKICAgIGNhbGxzdWIgaXNfcmljaAogICAgZHVwCiAgICBsZW4KICAgIGl0b2IKICAgIGV4dHJhY3QgNiAyCiAgICBzd2FwCiAgICBjb25jYXQKICAgIGJ5dGUgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2lzX2V2ZW5fcm91dGVAMzoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGJ0b2kKICAgIGNhbGxzdWIgaXNfZXZlbgogICAgZHVwCiAgICBsZW4KICAgIGl0b2IKICAgIGV4dHJhY3QgNiAyCiAgICBzd2FwCiAgICBjb25jYXQKICAgIGJ5dGUgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A2OgogICAgdHhuIE9uQ29tcGxldGlvbgogICAgYm56IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAMTAKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gaXMgY3JlYXRpbmcKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAMTA6CiAgICBpbnQgMAogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5JZkVsc2VFeGFtcGxlLmlzX3JpY2goYWNjb3VudF9iYWxhbmNlOiB1aW50NjQpIC0+IGJ5dGVzOgppc19yaWNoOgogICAgcHJvdG8gMSAxCiAgICBmcmFtZV9kaWcgLTEKICAgIGludCAxMDAwCiAgICA+CiAgICBieiBpc19yaWNoX2Vsc2VfYm9keUAyCiAgICBieXRlICJUaGlzIGFjY291bnQgaXMgcmljaCEiCiAgICByZXRzdWIKCmlzX3JpY2hfZWxzZV9ib2R5QDI6CiAgICBmcmFtZV9kaWcgLTEKICAgIGludCAxMDAKICAgID4KICAgIGJ6IGlzX3JpY2hfZWxzZV9ib2R5QDQKICAgIGJ5dGUgIlRoaXMgYWNjb3VudCBpcyBkb2luZyB3ZWxsLiIKICAgIHJldHN1YgoKaXNfcmljaF9lbHNlX2JvZHlANDoKICAgIGJ5dGUgIlRoaXMgYWNjb3VudCBpcyBwb29yIDooIgogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5JZkVsc2VFeGFtcGxlLmlzX2V2ZW4obnVtYmVyOiB1aW50NjQpIC0+IGJ5dGVzOgppc19ldmVuOgogICAgcHJvdG8gMSAxCiAgICBmcmFtZV9kaWcgLTEKICAgIGludCAyCiAgICAlCiAgICBibnogaXNfZXZlbl90ZXJuYXJ5X2ZhbHNlQDIKICAgIGJ5dGUgIkV2ZW4iCiAgICBiIGlzX2V2ZW5fdGVybmFyeV9tZXJnZUAzCgppc19ldmVuX3Rlcm5hcnlfZmFsc2VAMjoKICAgIGJ5dGUgIk9kZCIKCmlzX2V2ZW5fdGVybmFyeV9tZXJnZUAzOgogICAgcmV0c3ViCg==", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LklmRWxzZUV4YW1wbGUuY2xlYXJfc3RhdGVfcHJvZ3JhbToKICAgIGludCAxCiAgICByZXR1cm4K" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "IfElseExample", + "methods": [ + { + "name": "is_rich", + "args": [ + { + "type": "uint64", + "name": "account_balance" + } + ], + "returns": { + "type": "string" + } + }, + { + "name": "is_even", + "args": [ + { + "type": "uint64", + "name": "number" + } + ], + "returns": { + "type": "string" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +}""" +APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) +_TReturn = typing.TypeVar("_TReturn") + + +class _ArgsBase(ABC, typing.Generic[_TReturn]): + @staticmethod + @abstractmethod + def method() -> str: + ... + + +_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) + + +@dataclasses.dataclass(kw_only=True) +class _TArgsHolder(typing.Generic[_TArgs]): + args: _TArgs + + +def _filter_none(value: dict | typing.Any) -> dict | typing.Any: + if isinstance(value, dict): + return {k: _filter_none(v) for k, v in value.items() if v is not None} + return value + + +def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: + if data is None: + return {} + if not dataclasses.is_dataclass(data): + raise TypeError(f"{data} must be a dataclass") + if convert_all: + result = dataclasses.asdict(data) # type: ignore[call-overload] + else: + result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} + return _filter_none(result) + + +def _convert_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.TransactionParametersDict: + return typing.cast(algokit_utils.TransactionParametersDict, _as_dict(transaction_parameters)) + + +def _convert_call_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.OnCompleteCallParametersDict: + return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_create_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, + on_complete: algokit_utils.OnCompleteActionName, +) -> algokit_utils.CreateCallParametersDict: + result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) + on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" + result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) + return result + + +def _convert_deploy_args( + deploy_args: algokit_utils.DeployCallArgs | None, +) -> algokit_utils.ABICreateCallArgsDict | None: + if deploy_args is None: + return None + + deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) + if isinstance(deploy_args, _TArgsHolder): + deploy_args_dict["args"] = _as_dict(deploy_args.args) + deploy_args_dict["method"] = deploy_args.args.method() + + return deploy_args_dict + + +@dataclasses.dataclass(kw_only=True) +class IsRichArgs(_ArgsBase[str]): + account_balance: int + + @staticmethod + def method() -> str: + return "is_rich(uint64)string" + + +@dataclasses.dataclass(kw_only=True) +class IsEvenArgs(_ArgsBase[str]): + number: int + + @staticmethod + def method() -> str: + return "is_even(uint64)string" + + +@dataclasses.dataclass(kw_only=True) +class SimulateOptions: + allow_more_logs: bool = dataclasses.field(default=False) + allow_empty_signatures: bool = dataclasses.field(default=False) + extra_opcode_budget: int = dataclasses.field(default=0) + exec_trace_config: models.SimulateTraceConfig | None = dataclasses.field(default=None) + + +class Composer: + + def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): + self.app_client = app_client + self.atc = atc + + def build(self) -> AtomicTransactionComposer: + return self.atc + + def simulate(self, options: SimulateOptions | None = None) -> SimulateAtomicTransactionResponse: + request = models.SimulateRequest( + allow_more_logs=options.allow_more_logs, + allow_empty_signatures=options.allow_empty_signatures, + extra_opcode_budget=options.extra_opcode_budget, + exec_trace_config=options.exec_trace_config, + txn_groups=[] + ) if options else None + result = self.atc.simulate(self.app_client.algod_client, request) + return result + + def execute(self) -> AtomicTransactionResponse: + return self.app_client.execute_atc(self.atc) + + def is_rich( + self, + *, + account_balance: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `is_rich(uint64)string` ABI method + + :param int account_balance: The `account_balance` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = IsRichArgs( + account_balance=account_balance, + ) + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def is_even( + self, + *, + number: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `is_even(uint64)string` ABI method + + :param int number: The `number` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = IsEvenArgs( + number=number, + ) + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> "Composer": + """Adds a call to create an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_create( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return self + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> "Composer": + """Adds a call to the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass""" + + self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) + return self + + +class IfElseExampleClient: + """A class for interacting with the IfElseExample app providing high productivity and + strongly typed methods to deploy and call the app""" + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account | None = None, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + """ + IfElseExampleClient can be created with an app_id to interact with an existing application, alternatively + it can be created with a creator and indexer_client specified to find existing applications by name and creator. + + :param AlgodClient algod_client: AlgoSDK algod client + :param int app_id: The app_id of an existing application, to instead find the application by creator and name + use the creator and indexer_client parameters + :param str | Account creator: The address or Account of the app creator to resolve the app_id + :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by + creator and app name + :param AppLookup existing_deployments: + :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and + creator was passed as an Account will use that. + :param str sender: Address to use as the sender for all transactions, will use the address associated with the + signer if not specified. + :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should + *NOT* include the TMPL_ prefix + :param str | None app_name: Name of application to use when deploying, defaults to name defined on the + Application Specification + """ + + self.app_spec = APP_SPEC + + # calling full __init__ signature, so ignoring mypy warning about overloads + self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] + algod_client=algod_client, + app_spec=self.app_spec, + app_id=app_id, + creator=creator, + indexer_client=indexer_client, + existing_deployments=existing_deployments, + signer=signer, + sender=sender, + suggested_params=suggested_params, + template_values=template_values, + app_name=app_name, + ) + + @property + def algod_client(self) -> algosdk.v2client.algod.AlgodClient: + return self.app_client.algod_client + + @property + def app_id(self) -> int: + return self.app_client.app_id + + @app_id.setter + def app_id(self, value: int) -> None: + self.app_client.app_id = value + + @property + def app_address(self) -> str: + return self.app_client.app_address + + @property + def sender(self) -> str | None: + return self.app_client.sender + + @sender.setter + def sender(self, value: str) -> None: + self.app_client.sender = value + + @property + def signer(self) -> TransactionSigner | None: + return self.app_client.signer + + @signer.setter + def signer(self, value: TransactionSigner) -> None: + self.app_client.signer = value + + @property + def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: + return self.app_client.suggested_params + + @suggested_params.setter + def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: + self.app_client.suggested_params = value + + def is_rich( + self, + *, + account_balance: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[str]: + """Calls `is_rich(uint64)string` ABI method + + :param int account_balance: The `account_balance` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[str]: The result of the transaction""" + + args = IsRichArgs( + account_balance=account_balance, + ) + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def is_even( + self, + *, + number: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[str]: + """Calls `is_even(uint64)string` ABI method + + :param int number: The `number` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[str]: The result of the transaction""" + + args = IsEvenArgs( + number=number, + ) + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Creates an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.create( + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return result + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) + + def deploy( + self, + version: str | None = None, + *, + signer: TransactionSigner | None = None, + sender: str | None = None, + allow_update: bool | None = None, + allow_delete: bool | None = None, + on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, + on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, + template_values: algokit_utils.TemplateValueMapping | None = None, + create_args: algokit_utils.DeployCallArgs | None = None, + update_args: algokit_utils.DeployCallArgs | None = None, + delete_args: algokit_utils.DeployCallArgs | None = None, + ) -> algokit_utils.DeployResponse: + """Deploy an application and update client to reference it. + + Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator + account, including deploy-time template placeholder substitutions. + To understand the architecture decisions behind this functionality please see + + + ```{note} + If there is a breaking state schema change to an existing app (and `on_schema_break` is set to + 'ReplaceApp' the existing app will be deleted and re-created. + ``` + + ```{note} + If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') + the existing app will be deleted and re-created. + ``` + + :param str version: version to use when creating or updating app, if None version will be auto incremented + :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app + , if None uses self.signer + :param str sender: sender address to use when deploying app, if None uses self.sender + :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app + can be deleted + :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app + can be updated + :param OnUpdate on_update: Determines what action to take if an application update is required + :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements + has increased beyond the current allocation + :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys + should *NOT* include the TMPL_ prefix + :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application + :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application + :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application + :return DeployResponse: details action taken and relevant transactions + :raises DeploymentError: If the deployment failed""" + + return self.app_client.deploy( + version, + signer=signer, + sender=sender, + allow_update=allow_update, + allow_delete=allow_delete, + on_update=on_update, + on_schema_break=on_schema_break, + template_values=template_values, + create_args=_convert_deploy_args(create_args), + update_args=_convert_deploy_args(update_args), + delete_args=_convert_deploy_args(delete_args), + ) + + def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: + return Composer(self.app_client, atc or AtomicTransactionComposer()) diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/match_statements_client.py b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/match_statements_client.py new file mode 100644 index 0000000..8da3cab --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/match_statements_client.py @@ -0,0 +1,492 @@ +# flake8: noqa +# fmt: off +# mypy: disable-error-code="no-any-return, no-untyped-call, misc, type-arg" +# This file was automatically generated by algokit-client-generator. +# DO NOT MODIFY IT BY HAND. +# requires: algokit-utils@^1.2.0 +import base64 +import dataclasses +import decimal +import typing +from abc import ABC, abstractmethod + +import algokit_utils +import algosdk +from algosdk.v2client import models +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AtomicTransactionResponse, + SimulateAtomicTransactionResponse, + TransactionSigner, + TransactionWithSigner +) + +_APP_SPEC_JSON = r"""{ + "hints": { + "get_day(uint64)string": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0Lk1hdGNoU3RhdGVtZW50cy5hcHByb3ZhbF9wcm9ncmFtOgogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5NYXRjaFN0YXRlbWVudHMuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgcHJvdG8gMCAxCiAgICB0eG4gTnVtQXBwQXJncwogICAgYnogX19wdXlhX2FyYzRfcm91dGVyX19fYmFyZV9yb3V0aW5nQDUKICAgIG1ldGhvZCAiZ2V0X2RheSh1aW50NjQpc3RyaW5nIgogICAgdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAogICAgbWF0Y2ggX19wdXlhX2FyYzRfcm91dGVyX19fZ2V0X2RheV9yb3V0ZUAyCiAgICBpbnQgMAogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19nZXRfZGF5X3JvdXRlQDI6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICAhCiAgICBhc3NlcnQgLy8gT25Db21wbGV0aW9uIGlzIE5vT3AKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICBhc3NlcnQgLy8gaXMgbm90IGNyZWF0aW5nCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAxCiAgICBidG9pCiAgICBjYWxsc3ViIGdldF9kYXkKICAgIGR1cAogICAgbGVuCiAgICBpdG9iCiAgICBleHRyYWN0IDYgMgogICAgc3dhcAogICAgY29uY2F0CiAgICBieXRlIDB4MTUxZjdjNzUKICAgIHN3YXAKICAgIGNvbmNhdAogICAgbG9nCiAgICBpbnQgMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANToKICAgIHR4biBPbkNvbXBsZXRpb24KICAgIGJueiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDkKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gaXMgY3JlYXRpbmcKICAgIGludCAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2FmdGVyX2lmX2Vsc2VAOToKICAgIGludCAwCiAgICByZXRzdWIKCgovLyBzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0Lk1hdGNoU3RhdGVtZW50cy5nZXRfZGF5KGRhdGU6IHVpbnQ2NCkgLT4gYnl0ZXM6CmdldF9kYXk6CiAgICBwcm90byAxIDEKICAgIGZyYW1lX2RpZyAtMQogICAgc3dpdGNoIGdldF9kYXlfc3dpdGNoX2Nhc2VfMEAxIGdldF9kYXlfc3dpdGNoX2Nhc2VfMUAyIGdldF9kYXlfc3dpdGNoX2Nhc2VfMkAzIGdldF9kYXlfc3dpdGNoX2Nhc2VfM0A0IGdldF9kYXlfc3dpdGNoX2Nhc2VfNEA1IGdldF9kYXlfc3dpdGNoX2Nhc2VfNUA2IGdldF9kYXlfc3dpdGNoX2Nhc2VfNkA3CiAgICBieXRlICJJbnZhbGlkIGRheSIKICAgIHJldHN1YgoKZ2V0X2RheV9zd2l0Y2hfY2FzZV8wQDE6CiAgICBieXRlICJNb25kYXkiCiAgICByZXRzdWIKCmdldF9kYXlfc3dpdGNoX2Nhc2VfMUAyOgogICAgYnl0ZSAiVHVlc2RheSIKICAgIHJldHN1YgoKZ2V0X2RheV9zd2l0Y2hfY2FzZV8yQDM6CiAgICBieXRlICJXZWRuZXNkYXkiCiAgICByZXRzdWIKCmdldF9kYXlfc3dpdGNoX2Nhc2VfM0A0OgogICAgYnl0ZSAiVGh1cnNkYXkiCiAgICByZXRzdWIKCmdldF9kYXlfc3dpdGNoX2Nhc2VfNEA1OgogICAgYnl0ZSAiRnJpZGF5IgogICAgcmV0c3ViCgpnZXRfZGF5X3N3aXRjaF9jYXNlXzVANjoKICAgIGJ5dGUgIlNhdHVyZGF5IgogICAgcmV0c3ViCgpnZXRfZGF5X3N3aXRjaF9jYXNlXzZANzoKICAgIGJ5dGUgIlN1bmRheSIKICAgIHJldHN1Ygo=", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0Lk1hdGNoU3RhdGVtZW50cy5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgaW50IDEKICAgIHJldHVybgo=" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "MatchStatements", + "methods": [ + { + "name": "get_day", + "args": [ + { + "type": "uint64", + "name": "date" + } + ], + "returns": { + "type": "string" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +}""" +APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) +_TReturn = typing.TypeVar("_TReturn") + + +class _ArgsBase(ABC, typing.Generic[_TReturn]): + @staticmethod + @abstractmethod + def method() -> str: + ... + + +_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) + + +@dataclasses.dataclass(kw_only=True) +class _TArgsHolder(typing.Generic[_TArgs]): + args: _TArgs + + +def _filter_none(value: dict | typing.Any) -> dict | typing.Any: + if isinstance(value, dict): + return {k: _filter_none(v) for k, v in value.items() if v is not None} + return value + + +def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: + if data is None: + return {} + if not dataclasses.is_dataclass(data): + raise TypeError(f"{data} must be a dataclass") + if convert_all: + result = dataclasses.asdict(data) # type: ignore[call-overload] + else: + result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} + return _filter_none(result) + + +def _convert_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.TransactionParametersDict: + return typing.cast(algokit_utils.TransactionParametersDict, _as_dict(transaction_parameters)) + + +def _convert_call_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.OnCompleteCallParametersDict: + return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_create_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, + on_complete: algokit_utils.OnCompleteActionName, +) -> algokit_utils.CreateCallParametersDict: + result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) + on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" + result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) + return result + + +def _convert_deploy_args( + deploy_args: algokit_utils.DeployCallArgs | None, +) -> algokit_utils.ABICreateCallArgsDict | None: + if deploy_args is None: + return None + + deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) + if isinstance(deploy_args, _TArgsHolder): + deploy_args_dict["args"] = _as_dict(deploy_args.args) + deploy_args_dict["method"] = deploy_args.args.method() + + return deploy_args_dict + + +@dataclasses.dataclass(kw_only=True) +class GetDayArgs(_ArgsBase[str]): + date: int + + @staticmethod + def method() -> str: + return "get_day(uint64)string" + + +@dataclasses.dataclass(kw_only=True) +class SimulateOptions: + allow_more_logs: bool = dataclasses.field(default=False) + allow_empty_signatures: bool = dataclasses.field(default=False) + extra_opcode_budget: int = dataclasses.field(default=0) + exec_trace_config: models.SimulateTraceConfig | None = dataclasses.field(default=None) + + +class Composer: + + def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): + self.app_client = app_client + self.atc = atc + + def build(self) -> AtomicTransactionComposer: + return self.atc + + def simulate(self, options: SimulateOptions | None = None) -> SimulateAtomicTransactionResponse: + request = models.SimulateRequest( + allow_more_logs=options.allow_more_logs, + allow_empty_signatures=options.allow_empty_signatures, + extra_opcode_budget=options.extra_opcode_budget, + exec_trace_config=options.exec_trace_config, + txn_groups=[] + ) if options else None + result = self.atc.simulate(self.app_client.algod_client, request) + return result + + def execute(self) -> AtomicTransactionResponse: + return self.app_client.execute_atc(self.atc) + + def get_day( + self, + *, + date: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `get_day(uint64)string` ABI method + + :param int date: The `date` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = GetDayArgs( + date=date, + ) + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> "Composer": + """Adds a call to create an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_create( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return self + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> "Composer": + """Adds a call to the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass""" + + self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) + return self + + +class MatchStatementsClient: + """A class for interacting with the MatchStatements app providing high productivity and + strongly typed methods to deploy and call the app""" + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account | None = None, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + """ + MatchStatementsClient can be created with an app_id to interact with an existing application, alternatively + it can be created with a creator and indexer_client specified to find existing applications by name and creator. + + :param AlgodClient algod_client: AlgoSDK algod client + :param int app_id: The app_id of an existing application, to instead find the application by creator and name + use the creator and indexer_client parameters + :param str | Account creator: The address or Account of the app creator to resolve the app_id + :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by + creator and app name + :param AppLookup existing_deployments: + :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and + creator was passed as an Account will use that. + :param str sender: Address to use as the sender for all transactions, will use the address associated with the + signer if not specified. + :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should + *NOT* include the TMPL_ prefix + :param str | None app_name: Name of application to use when deploying, defaults to name defined on the + Application Specification + """ + + self.app_spec = APP_SPEC + + # calling full __init__ signature, so ignoring mypy warning about overloads + self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] + algod_client=algod_client, + app_spec=self.app_spec, + app_id=app_id, + creator=creator, + indexer_client=indexer_client, + existing_deployments=existing_deployments, + signer=signer, + sender=sender, + suggested_params=suggested_params, + template_values=template_values, + app_name=app_name, + ) + + @property + def algod_client(self) -> algosdk.v2client.algod.AlgodClient: + return self.app_client.algod_client + + @property + def app_id(self) -> int: + return self.app_client.app_id + + @app_id.setter + def app_id(self, value: int) -> None: + self.app_client.app_id = value + + @property + def app_address(self) -> str: + return self.app_client.app_address + + @property + def sender(self) -> str | None: + return self.app_client.sender + + @sender.setter + def sender(self, value: str) -> None: + self.app_client.sender = value + + @property + def signer(self) -> TransactionSigner | None: + return self.app_client.signer + + @signer.setter + def signer(self, value: TransactionSigner) -> None: + self.app_client.signer = value + + @property + def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: + return self.app_client.suggested_params + + @suggested_params.setter + def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: + self.app_client.suggested_params = value + + def get_day( + self, + *, + date: int, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[str]: + """Calls `get_day(uint64)string` ABI method + + :param int date: The `date` ABI parameter + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[str]: The result of the transaction""" + + args = GetDayArgs( + date=date, + ) + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Creates an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.create( + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return result + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) + + def deploy( + self, + version: str | None = None, + *, + signer: TransactionSigner | None = None, + sender: str | None = None, + allow_update: bool | None = None, + allow_delete: bool | None = None, + on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, + on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, + template_values: algokit_utils.TemplateValueMapping | None = None, + create_args: algokit_utils.DeployCallArgs | None = None, + update_args: algokit_utils.DeployCallArgs | None = None, + delete_args: algokit_utils.DeployCallArgs | None = None, + ) -> algokit_utils.DeployResponse: + """Deploy an application and update client to reference it. + + Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator + account, including deploy-time template placeholder substitutions. + To understand the architecture decisions behind this functionality please see + + + ```{note} + If there is a breaking state schema change to an existing app (and `on_schema_break` is set to + 'ReplaceApp' the existing app will be deleted and re-created. + ``` + + ```{note} + If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') + the existing app will be deleted and re-created. + ``` + + :param str version: version to use when creating or updating app, if None version will be auto incremented + :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app + , if None uses self.signer + :param str sender: sender address to use when deploying app, if None uses self.sender + :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app + can be deleted + :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app + can be updated + :param OnUpdate on_update: Determines what action to take if an application update is required + :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements + has increased beyond the current allocation + :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys + should *NOT* include the TMPL_ prefix + :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application + :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application + :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application + :return DeployResponse: details action taken and relevant transactions + :raises DeploymentError: If the deployment failed""" + + return self.app_client.deploy( + version, + signer=signer, + sender=sender, + allow_update=allow_update, + allow_delete=allow_delete, + on_update=on_update, + on_schema_break=on_schema_break, + template_values=template_values, + create_args=_convert_deploy_args(create_args), + update_args=_convert_deploy_args(update_args), + delete_args=_convert_deploy_args(delete_args), + ) + + def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: + return Composer(self.app_client, atc or AtomicTransactionComposer()) diff --git a/projects/python-contract-examples/smart_contracts/artifacts/control_flow/while_loop_example_client.py b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/while_loop_example_client.py new file mode 100644 index 0000000..aa3b224 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/artifacts/control_flow/while_loop_example_client.py @@ -0,0 +1,477 @@ +# flake8: noqa +# fmt: off +# mypy: disable-error-code="no-any-return, no-untyped-call, misc, type-arg" +# This file was automatically generated by algokit-client-generator. +# DO NOT MODIFY IT BY HAND. +# requires: algokit-utils@^1.2.0 +import base64 +import dataclasses +import decimal +import typing +from abc import ABC, abstractmethod + +import algokit_utils +import algosdk +from algosdk.v2client import models +from algosdk.atomic_transaction_composer import ( + AtomicTransactionComposer, + AtomicTransactionResponse, + SimulateAtomicTransactionResponse, + TransactionSigner, + TransactionWithSigner +) + +_APP_SPEC_JSON = r"""{ + "hints": { + "loop()uint64": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LldoaWxlTG9vcEV4YW1wbGUuYXBwcm92YWxfcHJvZ3JhbToKICAgIGNhbGxzdWIgX19wdXlhX2FyYzRfcm91dGVyX18KICAgIHJldHVybgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5jb250cm9sX2Zsb3cuY29udHJhY3QuV2hpbGVMb29wRXhhbXBsZS5fX3B1eWFfYXJjNF9yb3V0ZXJfXygpIC0+IHVpbnQ2NDoKX19wdXlhX2FyYzRfcm91dGVyX186CiAgICBwcm90byAwIDEKICAgIHR4biBOdW1BcHBBcmdzCiAgICBieiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANQogICAgbWV0aG9kICJsb29wKCl1aW50NjQiCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBfX3B1eWFfYXJjNF9yb3V0ZXJfX19sb29wX3JvdXRlQDIKICAgIGludCAwCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2xvb3Bfcm91dGVAMjoKICAgIHR4biBPbkNvbXBsZXRpb24KICAgICEKICAgIGFzc2VydCAvLyBPbkNvbXBsZXRpb24gaXMgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIGNhbGxzdWIgbG9vcAogICAgaXRvYgogICAgYnl0ZSAweDE1MWY3Yzc1CiAgICBzd2FwCiAgICBjb25jYXQKICAgIGxvZwogICAgaW50IDEKICAgIHJldHN1YgoKX19wdXlhX2FyYzRfcm91dGVyX19fYmFyZV9yb3V0aW5nQDU6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBibnogX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgIQogICAgYXNzZXJ0IC8vIGlzIGNyZWF0aW5nCiAgICBpbnQgMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDk6CiAgICBpbnQgMAogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLmNvbnRyb2xfZmxvdy5jb250cmFjdC5XaGlsZUxvb3BFeGFtcGxlLmxvb3AoKSAtPiB1aW50NjQ6Cmxvb3A6CiAgICBwcm90byAwIDEKICAgIGJ5dGUgIiIKICAgIGludCAxMAogICAgaW50IDAKCmxvb3Bfd2hpbGVfdG9wQDE6CiAgICBmcmFtZV9kaWcgMgogICAgZnJhbWVfYnVyeSAwCiAgICBmcmFtZV9kaWcgMQogICAgYnogbG9vcF9hZnRlcl93aGlsZUA3CiAgICBmcmFtZV9kaWcgMQogICAgaW50IDUKICAgID4KICAgIGJ6IGxvb3BfYWZ0ZXJfaWZfZWxzZUA0CiAgICBmcmFtZV9kaWcgMQogICAgaW50IDEKICAgIC0KICAgIGZyYW1lX2J1cnkgMQogICAgZnJhbWVfZGlnIDIKICAgIGludCAxCiAgICArCiAgICBmcmFtZV9idXJ5IDIKICAgIGIgbG9vcF93aGlsZV90b3BAMQoKbG9vcF9hZnRlcl9pZl9lbHNlQDQ6CiAgICBmcmFtZV9kaWcgMQogICAgaW50IDIKICAgIC0KICAgIGR1cAogICAgZnJhbWVfYnVyeSAxCiAgICBmcmFtZV9kaWcgMgogICAgaW50IDEKICAgICsKICAgIGZyYW1lX2J1cnkgMgogICAgaW50IDEKICAgID09CiAgICBieiBsb29wX3doaWxlX3RvcEAxCiAgICBmcmFtZV9kaWcgMgogICAgZnJhbWVfYnVyeSAwCgpsb29wX2FmdGVyX3doaWxlQDc6CiAgICByZXRzdWIK", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuY29udHJvbF9mbG93LmNvbnRyYWN0LldoaWxlTG9vcEV4YW1wbGUuY2xlYXJfc3RhdGVfcHJvZ3JhbToKICAgIGludCAxCiAgICByZXR1cm4K" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "WhileLoopExample", + "methods": [ + { + "name": "loop", + "args": [], + "returns": { + "type": "uint64" + } + } + ], + "networks": {} + }, + "bare_call_config": { + "no_op": "CREATE" + } +}""" +APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) +_TReturn = typing.TypeVar("_TReturn") + + +class _ArgsBase(ABC, typing.Generic[_TReturn]): + @staticmethod + @abstractmethod + def method() -> str: + ... + + +_TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) + + +@dataclasses.dataclass(kw_only=True) +class _TArgsHolder(typing.Generic[_TArgs]): + args: _TArgs + + +def _filter_none(value: dict | typing.Any) -> dict | typing.Any: + if isinstance(value, dict): + return {k: _filter_none(v) for k, v in value.items() if v is not None} + return value + + +def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: + if data is None: + return {} + if not dataclasses.is_dataclass(data): + raise TypeError(f"{data} must be a dataclass") + if convert_all: + result = dataclasses.asdict(data) # type: ignore[call-overload] + else: + result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} + return _filter_none(result) + + +def _convert_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.TransactionParametersDict: + return typing.cast(algokit_utils.TransactionParametersDict, _as_dict(transaction_parameters)) + + +def _convert_call_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, +) -> algokit_utils.OnCompleteCallParametersDict: + return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) + + +def _convert_create_transaction_parameters( + transaction_parameters: algokit_utils.TransactionParameters | None, + on_complete: algokit_utils.OnCompleteActionName, +) -> algokit_utils.CreateCallParametersDict: + result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) + on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" + result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) + return result + + +def _convert_deploy_args( + deploy_args: algokit_utils.DeployCallArgs | None, +) -> algokit_utils.ABICreateCallArgsDict | None: + if deploy_args is None: + return None + + deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) + if isinstance(deploy_args, _TArgsHolder): + deploy_args_dict["args"] = _as_dict(deploy_args.args) + deploy_args_dict["method"] = deploy_args.args.method() + + return deploy_args_dict + + +@dataclasses.dataclass(kw_only=True) +class LoopArgs(_ArgsBase[int]): + @staticmethod + def method() -> str: + return "loop()uint64" + + +@dataclasses.dataclass(kw_only=True) +class SimulateOptions: + allow_more_logs: bool = dataclasses.field(default=False) + allow_empty_signatures: bool = dataclasses.field(default=False) + extra_opcode_budget: int = dataclasses.field(default=0) + exec_trace_config: models.SimulateTraceConfig | None = dataclasses.field(default=None) + + +class Composer: + + def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): + self.app_client = app_client + self.atc = atc + + def build(self) -> AtomicTransactionComposer: + return self.atc + + def simulate(self, options: SimulateOptions | None = None) -> SimulateAtomicTransactionResponse: + request = models.SimulateRequest( + allow_more_logs=options.allow_more_logs, + allow_empty_signatures=options.allow_empty_signatures, + extra_opcode_budget=options.extra_opcode_budget, + exec_trace_config=options.exec_trace_config, + txn_groups=[] + ) if options else None + result = self.atc.simulate(self.app_client.algod_client, request) + return result + + def execute(self) -> AtomicTransactionResponse: + return self.app_client.execute_atc(self.atc) + + def loop( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> "Composer": + """Adds a call to `loop()uint64` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + args = LoopArgs() + self.app_client.compose_call( + self.atc, + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return self + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> "Composer": + """Adds a call to create an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns Composer: This Composer instance""" + + self.app_client.compose_create( + self.atc, + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return self + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> "Composer": + """Adds a call to the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass""" + + self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) + return self + + +class WhileLoopExampleClient: + """A class for interacting with the WhileLoopExample app providing high productivity and + strongly typed methods to deploy and call the app""" + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + @typing.overload + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + ... + + def __init__( + self, + algod_client: algosdk.v2client.algod.AlgodClient, + *, + creator: str | algokit_utils.Account | None = None, + indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, + existing_deployments: algokit_utils.AppLookup | None = None, + app_id: int = 0, + signer: TransactionSigner | algokit_utils.Account | None = None, + sender: str | None = None, + suggested_params: algosdk.transaction.SuggestedParams | None = None, + template_values: algokit_utils.TemplateValueMapping | None = None, + app_name: str | None = None, + ) -> None: + """ + WhileLoopExampleClient can be created with an app_id to interact with an existing application, alternatively + it can be created with a creator and indexer_client specified to find existing applications by name and creator. + + :param AlgodClient algod_client: AlgoSDK algod client + :param int app_id: The app_id of an existing application, to instead find the application by creator and name + use the creator and indexer_client parameters + :param str | Account creator: The address or Account of the app creator to resolve the app_id + :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by + creator and app name + :param AppLookup existing_deployments: + :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and + creator was passed as an Account will use that. + :param str sender: Address to use as the sender for all transactions, will use the address associated with the + signer if not specified. + :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should + *NOT* include the TMPL_ prefix + :param str | None app_name: Name of application to use when deploying, defaults to name defined on the + Application Specification + """ + + self.app_spec = APP_SPEC + + # calling full __init__ signature, so ignoring mypy warning about overloads + self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] + algod_client=algod_client, + app_spec=self.app_spec, + app_id=app_id, + creator=creator, + indexer_client=indexer_client, + existing_deployments=existing_deployments, + signer=signer, + sender=sender, + suggested_params=suggested_params, + template_values=template_values, + app_name=app_name, + ) + + @property + def algod_client(self) -> algosdk.v2client.algod.AlgodClient: + return self.app_client.algod_client + + @property + def app_id(self) -> int: + return self.app_client.app_id + + @app_id.setter + def app_id(self, value: int) -> None: + self.app_client.app_id = value + + @property + def app_address(self) -> str: + return self.app_client.app_address + + @property + def sender(self) -> str | None: + return self.app_client.sender + + @sender.setter + def sender(self, value: str) -> None: + self.app_client.sender = value + + @property + def signer(self) -> TransactionSigner | None: + return self.app_client.signer + + @signer.setter + def signer(self, value: TransactionSigner) -> None: + self.app_client.signer = value + + @property + def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: + return self.app_client.suggested_params + + @suggested_params.setter + def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: + self.app_client.suggested_params = value + + def loop( + self, + *, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + ) -> algokit_utils.ABITransactionResponse[int]: + """Calls `loop()uint64` ABI method + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.ABITransactionResponse[int]: The result of the transaction""" + + args = LoopArgs() + result = self.app_client.call( + call_abi_method=args.method(), + transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), + **_as_dict(args, convert_all=True), + ) + return result + + def create_bare( + self, + *, + on_complete: typing.Literal["no_op"] = "no_op", + transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, + ) -> algokit_utils.TransactionResponse: + """Creates an application using the no_op bare method + + :param typing.Literal[no_op] on_complete: On completion type to use + :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + result = self.app_client.create( + call_abi_method=False, + transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), + ) + return result + + def clear_state( + self, + transaction_parameters: algokit_utils.TransactionParameters | None = None, + app_args: list[bytes] | None = None, + ) -> algokit_utils.TransactionResponse: + """Calls the application with on completion set to ClearState + + :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters + :param list[bytes] | None app_args: (optional) Application args to pass + :returns algokit_utils.TransactionResponse: The result of the transaction""" + + return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) + + def deploy( + self, + version: str | None = None, + *, + signer: TransactionSigner | None = None, + sender: str | None = None, + allow_update: bool | None = None, + allow_delete: bool | None = None, + on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, + on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, + template_values: algokit_utils.TemplateValueMapping | None = None, + create_args: algokit_utils.DeployCallArgs | None = None, + update_args: algokit_utils.DeployCallArgs | None = None, + delete_args: algokit_utils.DeployCallArgs | None = None, + ) -> algokit_utils.DeployResponse: + """Deploy an application and update client to reference it. + + Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator + account, including deploy-time template placeholder substitutions. + To understand the architecture decisions behind this functionality please see + + + ```{note} + If there is a breaking state schema change to an existing app (and `on_schema_break` is set to + 'ReplaceApp' the existing app will be deleted and re-created. + ``` + + ```{note} + If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') + the existing app will be deleted and re-created. + ``` + + :param str version: version to use when creating or updating app, if None version will be auto incremented + :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app + , if None uses self.signer + :param str sender: sender address to use when deploying app, if None uses self.sender + :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app + can be deleted + :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app + can be updated + :param OnUpdate on_update: Determines what action to take if an application update is required + :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements + has increased beyond the current allocation + :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys + should *NOT* include the TMPL_ prefix + :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application + :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application + :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application + :return DeployResponse: details action taken and relevant transactions + :raises DeploymentError: If the deployment failed""" + + return self.app_client.deploy( + version, + signer=signer, + sender=sender, + allow_update=allow_update, + allow_delete=allow_delete, + on_update=on_update, + on_schema_break=on_schema_break, + template_values=template_values, + create_args=_convert_deploy_args(create_args), + update_args=_convert_deploy_args(update_args), + delete_args=_convert_deploy_args(delete_args), + ) + + def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: + return Composer(self.app_client, atc or AtomicTransactionComposer()) diff --git a/projects/python-contract-examples/smart_contracts/control_flow/contract.py b/projects/python-contract-examples/smart_contracts/control_flow/contract.py new file mode 100644 index 0000000..b5ae27e --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/control_flow/contract.py @@ -0,0 +1,99 @@ +import typing as t + +from algopy import ARC4Contract, String, UInt64, arc4, uenumerate, urange + + +# example: IF_ELSE +class IfElseExample(ARC4Contract): + + @arc4.abimethod + def is_rich(self, account_balance: UInt64) -> String: + if account_balance > 1000: + return String("This account is rich!") + elif account_balance > 100: + return String("This account is doing well.") + else: + return String("This account is poor :(") + + @arc4.abimethod + def is_even(self, number: UInt64) -> String: + return String("Even") if number % 2 == 0 else String("Odd") + + +# example: IF_ELSE + +# example: FOR_LOOP +FourArray: t.TypeAlias = arc4.StaticArray[arc4.UInt8, t.Literal[4]] + + +class ForLoopsExample(ARC4Contract): + + # urange: reversed items, forward index + @arc4.abimethod + def for_loop(self) -> FourArray: + array = FourArray(arc4.UInt8(0), arc4.UInt8(0), arc4.UInt8(0), arc4.UInt8(0)) + + for index, item in uenumerate(reversed(urange(4))): # [3, 2, 1, 0] + array[index] = arc4.UInt8(item) + + for index in urange(4): # [0, 1, 2, 3] + array[index] = arc4.UInt8(index) + return array + + +# example: FOR_LOOP + + +# example: MATCH +class MatchStatements(ARC4Contract): + + @arc4.abimethod + def get_day(self, date: UInt64) -> String: + + match date: + case UInt64(0): + return String("Monday") + case UInt64(1): + return String("Tuesday") + case UInt64(2): + return String("Wednesday") + case UInt64(3): + return String("Thursday") + case UInt64(4): + return String("Friday") + case UInt64(5): + return String("Saturday") + case UInt64(6): + return String("Sunday") + case _: + return String("Invalid day") + + +# example: MATCH + + +# example: WHILE_LOOP +class WhileLoopExample(ARC4Contract): + + @arc4.abimethod + def loop(self) -> UInt64: + + num = UInt64(10) + loop_count = UInt64(0) + + while num > 0: + if num > 5: + num -= 1 + loop_count += 1 + continue + + num -= 2 + loop_count += 1 + + if num == 1: + break + + return loop_count + + +# example: WHILE_LOOP diff --git a/projects/python-contract-examples/smart_contracts/control_flow/deploy_config.py b/projects/python-contract-examples/smart_contracts/control_flow/deploy_config.py new file mode 100644 index 0000000..76a2a69 --- /dev/null +++ b/projects/python-contract-examples/smart_contracts/control_flow/deploy_config.py @@ -0,0 +1,47 @@ +import logging + +import algokit_utils +import algokit_utils.application_client +from algosdk.v2client.algod import AlgodClient +from algosdk.v2client.indexer import IndexerClient + +from smart_contracts.artifacts.control_flow.for_loops_example_client import ( + ForLoopsExampleClient, +) +from smart_contracts.artifacts.control_flow.if_else_example_client import ( + IfElseExampleClient, +) +from smart_contracts.artifacts.control_flow.match_statements_client import ( + MatchStatementsClient, +) +from smart_contracts.artifacts.control_flow.while_loop_example_client import ( + WhileLoopExampleClient, +) + +logger = logging.getLogger(__name__) + + +# define deployment behaviour based on supplied app spec +def deploy( + algod_client: AlgodClient, + indexer_client: IndexerClient, + app_spec: algokit_utils.ApplicationSpecification, + deployer: algokit_utils.Account, +) -> None: + + app_clients = ( + ForLoopsExampleClient, + MatchStatementsClient, + WhileLoopExampleClient, + IfElseExampleClient, + ) + for client in app_clients: + app_client = client( + algod_client, + creator=deployer, + indexer_client=indexer_client, + ) + app_client.deploy( + on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, + on_update=algokit_utils.OnUpdate.AppendApp, + ) diff --git a/projects/python-contract-examples/tests/control_flow_integration_test.py b/projects/python-contract-examples/tests/control_flow_integration_test.py new file mode 100644 index 0000000..0119036 --- /dev/null +++ b/projects/python-contract-examples/tests/control_flow_integration_test.py @@ -0,0 +1,234 @@ +import pytest +from algokit_utils.beta.account_manager import AddressAndSigner +from algokit_utils.beta.algorand_client import ( + AlgorandClient, + PayParams, +) +from algokit_utils.config import config +from algosdk.v2client.algod import AlgodClient + +from smart_contracts.artifacts.control_flow import ( + for_loops_example_client, + if_else_example_client, + match_statements_client, + while_loop_example_client, +) + + +@pytest.fixture(scope="session") +def algorand() -> AlgorandClient: + """Get an AlgorandClient to use throughout the tests""" + algorand = AlgorandClient.default_local_net() + algorand.set_default_validity_window(1000) + + return algorand + + +@pytest.fixture(scope="session") +def dispenser(algorand: AlgorandClient) -> AddressAndSigner: + """Get the dispenser to fund test addresses""" + return algorand.account.dispenser() + + +@pytest.fixture(scope="session") +def creator(algorand: AlgorandClient, dispenser: AddressAndSigner) -> AddressAndSigner: + """Get an account to use as the creator of the inner transaction contract""" + acct = algorand.account.random() + + # Make sure the account has some ALGO + algorand.send.payment( + PayParams(sender=dispenser.address, receiver=acct.address, amount=10_000_000) + ) + + return acct + + +@pytest.fixture(scope="session") +def if_else_app_client( + algod_client: AlgodClient, creator: AddressAndSigner, algorand: AlgorandClient +) -> if_else_example_client.IfElseExampleClient: + """Deploy the inner txn App and create an app client the creator will use to interact with the contract""" + + config.configure( + debug=True, + # trace_all=True, + ) + + client = if_else_example_client.IfElseExampleClient( + algod_client, + sender=creator.address, + signer=creator.signer, + ) + + client.create_bare() + + algorand.send.payment( + PayParams( + sender=creator.address, + receiver=client.app_address, + amount=1000000, # 1 Algo + ) + ) + + print(f"Inner Txn app ID: {client.app_id}") + return client + + +@pytest.fixture(scope="session") +def for_loop_app_client( + algod_client: AlgodClient, creator: AddressAndSigner, algorand: AlgorandClient +) -> for_loops_example_client.ForLoopsExampleClient: + """Deploy the inner txn App and create an app client the creator will use to interact with the contract""" + + config.configure( + debug=True, + # trace_all=True, + ) + + client = for_loops_example_client.ForLoopsExampleClient( + algod_client, + sender=creator.address, + signer=creator.signer, + ) + + client.create_bare() + + algorand.send.payment( + PayParams( + sender=creator.address, + receiver=client.app_address, + amount=1000000, # 1 Algo + ) + ) + + print(f"Inner Txn app ID: {client.app_id}") + return client + + +@pytest.fixture(scope="session") +def match_statements_app_client( + algod_client: AlgodClient, creator: AddressAndSigner, algorand: AlgorandClient +) -> match_statements_client.MatchStatementsClient: + """Deploy the inner txn App and create an app client the creator will use to interact with the contract""" + + config.configure( + debug=True, + # trace_all=True, + ) + + client = match_statements_client.MatchStatementsClient( + algod_client, + sender=creator.address, + signer=creator.signer, + ) + + client.create_bare() + + algorand.send.payment( + PayParams( + sender=creator.address, + receiver=client.app_address, + amount=1000000, # 1 Algo + ) + ) + + print(f"Inner Txn app ID: {client.app_id}") + return client + + +@pytest.fixture(scope="session") +def while_loop_app_client( + algod_client: AlgodClient, creator: AddressAndSigner, algorand: AlgorandClient +) -> while_loop_example_client.WhileLoopExampleClient: + """Deploy the inner txn App and create an app client the creator will use to interact with the contract""" + + config.configure( + debug=True, + # trace_all=True, + ) + + client = while_loop_example_client.WhileLoopExampleClient( + algod_client, + sender=creator.address, + signer=creator.signer, + ) + + client.create_bare() + + algorand.send.payment( + PayParams( + sender=creator.address, + receiver=client.app_address, + amount=1000000, # 1 Algo + ) + ) + + print(f"Inner Txn app ID: {client.app_id}") + return client + + +def test_if_else( + if_else_app_client: if_else_example_client.IfElseExampleClient, +) -> None: + + inputs = [ + (1001, "This account is rich!"), + (101, "This account is doing well."), + (50, "This account is poor :("), + ] + + for input_value in inputs: + txn_result = if_else_app_client.is_rich(account_balance=input_value[0]) + print(f"is_rich result: {txn_result.return_value}") + + assert txn_result.return_value == input_value[1] + print(f"if_else result: {txn_result.return_value}") + + inputs = [(1, "Odd"), (2, "Even")] + for i in inputs: + txn_result = if_else_app_client.is_even(number=i[0]) + print(f"is_even result: {txn_result.return_value}") + + assert txn_result.return_value == i[1] + + +def test_for_loops( + for_loop_app_client: for_loops_example_client.ForLoopsExampleClient, +) -> None: + + txn_result = for_loop_app_client.for_loop() + print(f"for_loop result: {txn_result.return_value}") + + assert txn_result.return_value == [0, 1, 2, 3] + + +def test_match_statements( + match_statements_app_client: match_statements_client.MatchStatementsClient, +) -> None: + + inputs = [ + (0, "Monday"), + (1, "Tuesday"), + (2, "Wednesday"), + (3, "Thursday"), + (4, "Friday"), + (5, "Saturday"), + (6, "Sunday"), + (7, "Invalid day"), + ] + + for i in inputs: + txn_result = match_statements_app_client.get_day(date=i[0]) + print(f"get_day result: {txn_result.return_value}") + + assert txn_result.return_value == i[1] + + +def test_while_loop( + while_loop_app_client: while_loop_example_client.WhileLoopExampleClient, +) -> None: + + txn_result = while_loop_app_client.loop() + print(f"loop result: {txn_result.return_value}") + + assert txn_result.return_value == 7