-
Notifications
You must be signed in to change notification settings - Fork 132
Closed
Labels
Description
Problem: Passing Local ScratchVars Inside Subroutines By Reference Recursively DOES NOT WORK
Consider the following PyTeal Code
From a purely mathemetical perspective, it is a valid way to compute the factorial of n: n!
@Subroutine(TealType.none)
def factorial_BAD(n: ScratchVar):
tmp = ScratchVar(TealType.uint64)
return (
If(n.load() <= Int(1))
.Then(n.store(Int(1)))
.Else(
Seq(
tmp.store(n.load() - Int(1)),
factorial_BAD(tmp),
n.store(n.load() * tmp.load()),
)
)
)
# approval program:
def fac_by_ref_BAD():
n = ScratchVar(TealType.uint64)
return Seq(
n.store(Int(10)),
factorial_BAD(n),
n.load(),
)Generated TEAL:
#pragma version 6
int 10
store 0
int 0
callsub factorialBAD_0
load 0
return
// factorial_BAD
factorialBAD_0:
store 1
load 1
loads
int 1
<=
bnz factorialBAD_0_l2
load 1
loads
int 1
-
store 2
int 2
load 1
load 2
uncover 2
callsub factorialBAD_0
store 2
store 1
load 1
load 1
loads
load 2
*
stores
b factorialBAD_0_l3
factorialBAD_0_l2:
load 1
int 1
stores
factorialBAD_0_l3:
retsub
Dry Run Results:
Instead of computing 10!, only the first product 10*9 == 90 is computed:
App Messages: ['ApprovalProgram', 'PASS']
App Logs: None
App Trace:
pc# line# source stack
1 1 intcblock 1 []
4 2 pushint 10 []
...
64 39 retsub []
13 6 load 0 []
15 7 return [90]
65 40 [90]
But, we CAN MODIFY the Above to Only Pass Through Subroutine Parameters Var's By-Reference
And then everything works!
PyTeal:
@Subroutine(TealType.none)
def factorial(n: ScratchVar):
tmp = ScratchVar(TealType.uint64)
return (
If(n.load() <= Int(1))
.Then(n.store(Int(1)))
.Else(
Seq(
tmp.store(n.load()),
n.store(n.load() - Int(1)),
factorial(n),
n.store(n.load() * tmp.load()),
)
)
)
# approval program:
def fac_by_ref():
n = ScratchVar(TealType.uint64)
return Seq(
n.store(Int(10)),
factorial(n),
n.load(),
)Generated TEAL
#pragma version 6
int 10
store 0
int 0
callsub factorial_0
load 0
return
// factorial
factorial_0:
store 1
load 1
loads
int 1
<=
bnz factorial_0_l2
load 1
loads
store 2
load 1
load 1
loads
int 1
-
stores
load 1
load 1
load 2
uncover 2
callsub factorial_0
store 2
store 1
load 1
load 1
loads
load 2
*
stores
b factorial_0_l3
factorial_0_l2:
load 1
int 1
stores
factorial_0_l3:
retsub
Dry Run Results
App Messages: ['ApprovalProgram', 'PASS']
App Logs: None
App Trace:
pc# line# source stack
...
70 43 retsub []
13 6 load 0 []
15 7 return [3628800]
71 44 [3628800]
Solution:
- Raise a
TealInputErrorWhen a Local ScratchVar inside a Subroutine is passed into another Subroutine - Verify via Semantic E2E Tests on tons of use cases, that correctly structured programs work as expected
Dependencies
#214 (No. 2 only). Certainly Goal No. 1 can go in as a separate PR.
Urgency
Medium to High: We don't want PyTeal users to shoot themselves in the foot. On the other hand, I'm not sure how often PyTeal users would be using the brand new capability of passing by-ref which was only introduced 3/1/2022 (#183 ).