algorand / pyteal

Algorand Smart Contracts in Python
https://pyteal.readthedocs.io
MIT License
285 stars 131 forks source link

Continue skips while loop condition check #331

Closed StylishTriangles closed 2 years ago

StylishTriangles commented 2 years ago

Subject of the issue

Continue() statement skips the check inside the while loop.

Your environment

pyteal 0.12.1

Steps to reproduce

Running the script below:

from pyteal import *

i = ScratchVar(slotId=10)
ast = Seq(
    i.store(Int(0)),
    While(i.load() < Int(30)).Do(
        Seq(
            i.store(i.load() + Int(1)),
            Continue(),
        )
    ),
    Return(Int(1))
)

comp = compileTeal(ast, Mode.Application, version=6)

print(comp)

...prints the following TEAL:

#pragma version 6
int 0
store 10
load 10
int 30
<
bz main_l2
main_l1:
load 10
int 1
+
store 10
b main_l1
main_l2:
int 1
return

Note, that main_l1: is just after the condition check, therefore the loop never terminates (it technically does after 18446744073709551615 iterations, but that's an edge case I'm not concerned about).

Expected behaviour

I expect the PyTEAL compiled code to actually terminate, to better portray what I mean, I have created the following script in Python:

i = 0
while i < 30:
    i += 1
    continue

# control reaches here, therefore the program halts 🥳 

Actual behaviour

∞ lööp

Extra info

Continue() works as expected inside the For loop

ahangsu commented 2 years ago

Hi! Thanks for the bug catch!

I think the bug is caused by this line: https://github.com/algorand/pyteal/blob/master/pyteal/ast/while_.py#L55, where we wired the continue block back to the start of do-block. The fix should be changing such line to set block's next block to be condition's start, i.e.,

        for block in continueBlocks:
            block.setNextBlock(condStart)

In this way, the while-cond check can be executed after continue is executed, which should follow your expected behavior. The resulting TEAL code (after such oneline fix) is attached

#pragma version 6
int 0
store 10
main_l1:
load 10
int 30
<
bz main_l3
load 10
int 1
+
store 10
b main_l1
main_l3:
int 1
return
ahangsu commented 2 years ago

Fixed in #332

StylishTriangles commented 2 years ago

Thanks a lot for the swift fix!