Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get rid of MaybeValue #139

Closed
algoanne opened this issue Nov 19, 2021 · 1 comment · Fixed by #196
Closed

Get rid of MaybeValue #139

algoanne opened this issue Nov 19, 2021 · 1 comment · Fixed by #196
Assignees

Comments

@algoanne
Copy link

algoanne commented Nov 19, 2021

Background

PyTeal has a requirement that every expression must push either 0 or 1 new elements to the stack. However, some opcodes, like asset_holding_get, asset_param_get, app_param_get, app_global_get_ex, and app_local_get_ex push 2 new elements to the stack.

In order to handle these opcodes PyTeal uses MaybeValue to contain both elements being pushed to the stack. Internally, MaybeValue allocates two scratch slots and pops the 2 elements into these slots. Then later when .hasValue or .value is called on that MaybeValue, it loads the value from the appropriate scratch slot.

Problem

While the above approach works technically, the existing MaybeValue type is not the easiest to use. This is because you must create a temporary variable to store the MaybeValue and explicitly load it before using it. For example:

myStatus = App.globalGetEx(Txn.applications[1], Bytes("status"))

program = Seq(
    myStatus,
    If(myStatus.hasValue(), myStatus.value(), Bytes("none"))
)

The requirement to define a top-level Python variable in order to hold the intermediate value of a MaybeValue means that it does not compose well in other PyTeal expressions. Additionally, it's especially annoying if you only want to examine one of the returned values.

For these reasons, we should introduce a new way for PyTeal to handle operations which push multiple values to the stack.

Solution

The proposed solution is to create a new expression type to represent the return values from opcodes that push 2 or more values to the stack. Let's call this expression type MultiValue.

Instead of always storing the stack values in scratch slots like MaybeValue currently does, MultiValue will allow the user to specify how each return value should be handled, avoiding needing an intermediate storage area for the values and closing the gap between loading and using the values.

This can be achieved through a user-defined reducer function. The number of arguments for the reducer function is the same as the number of elements that operation pushes to the stack, and the reducer function will operate on these elements, eventually returning a PyTeal expression which will result in either 0 or 1 elements being pushed to the stack.

Compatibility

In order to maintain compatibility with existing usage of MaybeValue, we can make MaybeValue a subclass of MultiValue. MaybeValue will continue to have the same usage, unless you call the .reduce function on it, in which case it will behave like a MultiValue instead.

Examples

To store the values in scratch slots, similar to the existing MaybeValue implementation, you could do the following:

hasValueVar = ScratchVar(TealType.uint64)
valueVar = ScratchVar(TealType.uint64)
Seq(
    App.globalGetEx(Txn.applications[1], Bytes("status"))
       .reduce(lambda hasValue, value: Seq(hasValueVar.store(hasValue), valueVar.store(hasValue))),
    If(hasValue.load(), value.load(), Bytes("none"))
)

Or if you don't need to keep the values for later, you don't need to use scratch variables at all. You could put your condition directly inside of the reducer function:

status = App.globalGetEx(Txn.applications[1], Bytes("status"))
    .reduce(lambda hasValue, value: If(hasValue).Then(value).Else(Bytes("none")))

Instead, if you want to assert that hasValue is always true, you could do:

status = App.globalGetEx(Txn.applications[1], Bytes("status"))
    .reduce(lambda hasValue, value: Seq(Assert(hasValue), value))

Further usage

Since MultiValue provides a general way of handing opcodes that push multiple values to the stack, we can directly expose more opcodes that behave this way, like addw, mulw, divmodw, expw, ecdsa_pk_decompress, and ecdsa_pk_recover.

TODO: think more about how wide math operations can be most conveniently used in this proposal

@algoanne
Copy link
Author

maybe we should use some naming other than reduce because there's already a python meaning for reduce which is not the same as this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants