Supported Python subset#

Python and Michelson have very different semantics.

Our constraints are:

  • A Pymich typechecked program should always typecheck in MyPy +++
    • This allows IDEs to signal most, if not all, typechecking errors.

    • Type errors that are caught by Pymich but not MyPy should throw at runtime when the smart contract is ran in CPython.

  • How are closures handled?

  • How to distinguish between signed and unsigned integers?

  • How to play nice with python LSP autocompletes? (are we really going to compile Python’s List.append?)

+++ since MyPy cannot be as restrictive than Pymich, i.e. valid MyPy typechecks can be invalid in Pymich

All of these problems can be solved by using immutable datastructures.

Note

Python’s dynamic features are not supported. Indeed, since Pymich is statically compiled, some of these features are impossible to compile efficiently. Hence, think of Pymich as a static subset of Python’s features.

Immutable data structures#

Pymich is as simple Python immutable data structures library

Which makes sense, to each tool its job:

  • ORM’s have their own abstractions to manipulate DBs

  • Numerical lib’s have their own abstractions to manipulate array of numbers (ex: np.array)

  • Pymich has its own abstractions to manipulate Michelson datastructures, that are fundamentally immutable.

All datastructures are documented here.

Closures#

Keeping in mind that:

  • Since we are using immutable datastructures,

  • and reassigning variables are prevented in Python unless global, local or nonlocal keywords are used,

  • then dissallowing them solves the problem of compiling closure reassignments.

  • these keywords are so rarely used in Python, that this should not matter much

Closures#

Example#

k = Nat(10)

def f(x: Nat) -> Nat:
    return x + k

def g(x: Nat) -> Nat:
    return k * x

def compose(x: Nat) -> Nat:
    return k * f(g(x))

result = compose(k)

Counter-example#

Since Pymich closures partially apply variables in closures, the can not be mutated after the closure is defined to preserve. Pymich should throw an exception in strict mode and a warning otherwise:

i = Nat(1)
def f(x: Nat) -> Nat:
    return x + i
i = Nat(2)

result = f(10)
# CPython returns Nat(12)
# Pymich retuns Nat(11)

Inter contract calls#

The Contract class exposes an ops attribute of type Operations. It is just a Michelson list of operations that only allows adding elements by pushing. Under the hood, the compiler prepends all transactions and inverses the operation list befor returning it.

This allows for constructing operations through functions.

The following code typechecks in MyPy:

@dataclass
class MintOrBurn(Record):
    quantity: Int
    target: Address


def mint_or_burn(
        ops: Operations,
        lqt_address: Address,
        target: Address,
        quantity: Int,
    ) -> Operations:
    mint_or_burn_entrypoint = Contract[MintOrBurn](lqt_address, "%transfer")
    return ops.push(
        mint_or_burn_entrypoint,
        Mutez(0),
        MintOrBurn(quantity, target),
    )


class Proxy(Contract):
    def mint_or_burn(self, x: Nat) -> None:
        self.ops = mint_or_burn(self.ops, token_address, owner, Int(10))

Mypy typechecking#

The Operations class type annotated in such a way that Mypy fails in the following example since MintOrBurn(quantity, target) is not of type Nat:

mint_or_burn_entrypoint = Contract[Nat](lqt_address, "%transfer")
return ops.push(
    mint_or_burn_entrypoint,
    Mutez(0),
    MintOrBurn(quantity, target),
)

Exceptions#

Exceptions will be compiled as Michelson PUSH string "my error message" ; FAILWITH instructions and can be raised as expected:

def floor_div(num: Nat, denom: Nat):
    if denom == Nat(0):
        raise Exception("Cannot divide by 0!")
    else:
        return num // denom

Note

Note that Exception takes a Python string as argument rather than a Michelson string as this was more convenient than writting Exception(String("My error message")).

Python unsupported by Pymich#

  • passing self as a function parameter

  • return anywhere other than as the last expression of a function body