Skip to content

Commit

Permalink
Some very very basic docs + specify minimum python version for Puya i…
Browse files Browse the repository at this point in the history
…tself (#7)

- bring in principles and ADR context docs from old wyvern repo & link it from README
- remove the old 🧠💨 dumping ground
- add some basic info to README, including calling out links to actually readable/useful examples
- update minimum Python to reflect minimum intended version for Puya itself
  • Loading branch information
achidlow authored Dec 11, 2023
1 parent 6baa9c3 commit ed22176
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 70 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/check-python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
- name: Install poetry
run: pipx install poetry

- name: Set up Python 3.11
- name: Set up Python 3.12
uses: actions/setup-python@v4
with:
python-version: "3.11"
python-version: "3.12"
cache: "poetry"

- name: Install dependencies
Expand All @@ -46,8 +46,8 @@ jobs:
- name: Stop LocalNet
run: algokit localnet stop

- name: pytest coverage comment - using Python 3.11 on ubuntu-latest
if: matrix.python == '3.11' && matrix.os == 'ubuntu-latest'
- name: pytest coverage comment - using Python 3.12 on ubuntu-latest
if: matrix.python == '3.12' && matrix.os == 'ubuntu-latest'
continue-on-error: true # forks fail to add a comment, so continue any way
uses: MishaKav/pytest-coverage-comment@main
with:
Expand Down
94 changes: 32 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,46 @@
# Puya - Algorand TEAL compiler + Python language bindings
# Puya - Algorand TEAL compiler + AlgoPy Python language bindings

```shell
poetry install
poetry shell
# Run the compiler
puya examples/amm
# OR compile all examples
python -m scripts.compile_all_examples
# OR run tests
pytest
```
[Project background and guiding principles](docs/principles.md).

## Existing README stuff - needs clean up
## Installation

TODO:
How to handle identity for compound objects i.e. dataclasses so that semantic compatability is retained
Ideas:
Enforce value equality (dataclasses implement data equality by default)
Could carry a unique identifier for the object to represent it's identity
The minimum supported Python version for running Puya itself is 3.12.

how to handle user libs with type stubs:
option: don't. only want to handle algopy specific code anyway, which needs
to be fully typed.
You can install the developer preview of Puya directly from this repo by using [pipx](https://pipx.pypa.io/stable/):
```shell
pipx install git+https://github.com/algorandfoundation/puya.git
```

Or you can clone the repo and have a poke around without installing it globally.

THOUGHTS:
- mypy call expr resolves to full node (ie FuncDef) when not through member ref
- user classes (ie not contracts) can't be allowed to override (maybe not even define??) methods, unless we want to implement vtables..................
```shell
git clone https://github.com/algorandfoundation/puya.git
cd puya
poetry install
poetry shell
```

Note that with this method you'll need to activate the virtual environment created by poetry
before using the puya command in each new shell that you open - you can do this by running
`poetry shell` in the `puya` directory.

we probably want to vendor mypy, for a few reasons:
1) we want to pin to an exact version, without possibly getting in the way of user updating their venv version and/or CI/CD config changes
2) we need to stop mypy from being compiled with mypyc, which I don't think we can do with a wheel package? and
3) even if we can/could stop it being compiled, it slows things down a lot for the user, so that's not a great experience
4) can simplify our debugging/development experience (not really a reason by itself, but a nice bonus)
To check that you can run the `puya` command successfully after that, you can run the help command:

mypy is MIT licensed, except for some files in mypy/mypyc
git submodules could be one approach here, but we can't point it to a subdirectory (ie mypy/mypy)
we can possibly exclude these after the fact in our build to prevent our package becoming too larg
and/or licensing violations, but the import paths might get a bit verbose (ie wyvern.vendor(?).mypy.mypy).
maybe a manual approach is best, possibly with a script to do the update.
we definitely don't want any alterations to the mypy code base, except perhaps in the most dire of
circumstances where we need to patch a particular bug (but really, even then, we should contribute this back upstream ASAP)
`puya -h`

To compile a contract or contracts, just supply the path(s) - either to the .py files themselves,
or the containing directories. In the case of containing directories, any contracts discovered
therein will be compiled, allowing you to compile multiple contracts at once. You can also supply
more than one path at a time to the compiler.

## Language guide

TODOs:
error handling at expression level, needs stub data. current handling could generate spurious extra errors
Coming soon! For now, refer to the examples to see what's possible.

## Examples

### Parking lot
- match statements
- efficient compound types including pass-by-ref using bytes[] as scratch pointers with loadss/storess
- type tags at end of bytes[] pointers, allowing union types as args and isinstance checks
- inlineable functions either via explicit hints or via optimisation
- improved constant folding when encountering a non-literal (e.g UInt64) that wraps a known-constant value
- support properties - probably requires inlining?
- More Pythonic ways of using Txn et al:
- `for arg in Txna.application_args`
- `len(Txna.application_args)`
- `Txna.application_args[0]`
- for functions like `box_get` that return `value, did exist: bool` - translate to `value | None`
- support for user defined Protocols (PEP 544)
- support user reserved scratch slots (and syntax to load / store values)
- `gloads` allows an application to query another transaction's scratch space, in order for this to be useful a wyvern built app needs a way to push values to `well known` slots
- on reference to `app_(local|global)_get(_ex)?` et. al. extract keys if literals,
and if dynamic then warn if storage schema not specified explicitly
- allow overrides of local and global storage keys... somehow. or maybe we allow compacting them
for more storage space as a compiler flag? this would be less helpful though if you wanted to
read them off chain directly...
- allow more idiomatic `is not None` for `Local.__getitem__` result rather than returning a tuple
- similarly, allow global (ie member vars) to be ` | None = None` ??
- default values for subroutine arguments
- optimize large x in (...y) ops to use `match` op
-
- [voting](examples/voting/voting.py)
- [AMM](examples/amm/contract.py)
- [auction](examples/TEALScript/auction/contract.py)
- [non-ABI calculator](examples/calculator/contract.py)
- [local storage](examples/local_storage)
180 changes: 180 additions & 0 deletions docs/principles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@

# Background

**Smart contracts** on the Algorand blockchain run on the Algorand Virtual Machine ([AVM](https://developer.algorand.org/docs/get-details/dapps/avm/)).
This is a stack based virtual machine, which executes AVM bytecode as part of an [Application Call transaction](https://developer.algorand.org/docs/get-details/transactions/#application-call-transaction).
The official mechanism for generating this bytecode is by submitting TEAL (Transaction Execution Approval Language) to
an Algorand Node to compile.

**Smart signatures** have the same basis in the AVM and TEAL, but have a different execution model, one not involving
Application Call transactions. Our focus will primarily be on smart contracts, since they are strictly more powerful
in terms of available AVM functions.

TEAL is a [non-structured](https://en.wikipedia.org/wiki/Non-structured_programming)
[imperative language](https://en.wikipedia.org/wiki/Procedural_programming#Imperative_programming)
(albeit one with support for procedure calls that can isolate stack changes since v8 with `proto`). Writing TEAL is very
similar to writing assembly code. It goes without saying that this is a particularly common or well-practiced model for
programming these days.

As it stands today, developers wanting to write smart contracts specifically for Algorand have the option of writing
TEAL directly, or using some other mechanism of generating TEAL such as the officially supported [PyTEAL](https://pyteal.readthedocs.io/en/stable/)
or the community supported [tealish](https://tealish.tinyman.org/en/latest/).

PyTEAL follows a [generative programming](https://en.wikipedia.org/wiki/Automatic_programming#Generative_programming) paradigm,
which is a form of metaprogramming. Naturally, writing programs to generate programs presents an additional hurdle for
developers looking to pick up smart contract development. Tooling support for this is also suboptimal, for example, many
classes of errors resulting from the interaction between the procedural elements of the Python language and the PyTEAL
expression-building framework go unnoticed until the point of TEAL generation, or worse go completely unnoticed, and even
when PyTEAL can/does provide an error it can be difficult to understand.

Tealish provides a higher level procedural language, bearing a passing resemblance to Python, than compiles down to TEAL.
However, it's still lower level than most developers are used to.
For example, the expression `1 + 2 + 3`is [not valid in tealish](https://tealish.tinyman.org/en/latest/language.html#math-logic).
Another difference vs a higher level language such as Python is that [functions can only be declared after the program
entry point logic](https://tealish.tinyman.org/en/latest/language.html#functions).
In essence, tealish abstracts away many difficulties with writing plain TEAL,
but it is still essentially more of a transpiler than a compiler.
Furthermore, whilst appearing to have syntax inspired by Python, it both adds and removes many fundamental syntax elements,
presenting an additional learning curve to developers looking to learn blockchain development on Algorand.
Being a bespoke language also means it has a much smaller ecosystem of tooling built around it compared to languages like
Python or JavaScript.

To most developers, the Python programming language needs no introduction. First released in 1991, it's popularity has
grown steadily over the decades, and as of June 2023 it is consistently ranked as either the most popular langauge,
or second most popular following JavaScript:

- https://octoverse.github.com/2022/top-programming-languages
- https://stackoverflow.blog/2023/06/13/developer-survey-results-are-in/
- https://www.tiobe.com/tiobe-index/
- https://pypl.github.io/PYPL.html

The AlgoKit project is an Algorand Foundation initiative to improve the developer experience on Algorand. Within this
broad remit, two of the key [principles](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles)
are to "meet developers where they are" and "leverage existing ecosystem".
Building a compiler that allows developers to write smart contracts using an idiomatic subset of a high level language
such as Python would make great strides towards both of these goals.

Wyvern was the original internal code name for just such a compiler (now called Puya), one that will transform Python code into valid TEAL
smart contracts. In line with the principle of meeting developers where they are, and recognising the popularity of
JavaScript and TypeScript, a parallel initiative to build a TypeScript to TEAL compiler is [also underway](https://tealscript.netlify.app).

# Principles

The principles listed here should form the basis of our decision-making, both in the design and implementation.

## Inherited from AlgoKit

As a part of the AlgoKit project, the principles outlined [there](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles)
also apply - to the extent that this project is just one component of AlgoKit.

### "Leverage existing ecosystem"

> AlgoKit functionality gets into the hands of Algorand developers quickly by building on top of the
> existing ecosystem wherever possible and aligned to these principles.
In order to leverage as much existing Python tooling as possible, we should strive to maintain the highest level of
compatibility with the Python language (and the reference implementation: CPython).

### "Meet developers where they are"

> Make Blockchain development mainstream by giving all developers an idiomatic development experience in the operating
> system, IDE and language they are comfortable with so they can dive in quickly and have less they need to learn before
> being productive.
Python is a very idiomatic language. We should embrace accepted patterns and practices as much as possible,
such as those listed in [PEP-20](https://peps.python.org/pep-0020/) (aka "The Zen of Python").

### "Extensible"

> Be extensible for community contribution rather than stifling innovation, bottle-necking all changes through the
> Algorand Foundation and preventing the opportunity for other ecosystems being represented (e.g. Go, Rust, etc.).
> This helps make developers feel welcome and is part of the developer experience, plus it makes it easier to add
> features sustainably
One way to support this principle in the broader AlgoKit context is by building in a mechanism for reusing
common code between smart contracts, to allow the community to build their own Python packages.

### "Sustainable"

> AlgoKit should be built in a flexible fashion with long-term maintenance in mind. Updates to latest patches in
> dependencies, Algorand protocol development updates, and community contributions and feedback will all feed in to the
> evolution of the software.
Taking this principle further, ensuring the compiler is well-designed (e.g. frontend backend separation,
with a well-thought-out IR) will help with maintaining and improving the implementation over time. For example,
adding in new TEAL language features will be easier, same for implementing new optimisation strategies.

Looking to the future, best practices for smart contract development are rapidly evolving. We shouldn't tie the
implementation too tightly to a current standard such as ARC-4 - although in that specific example, we would still
aim for first class support, but it shouldn't be assumed as the only way to write smart contracts.


### "Modular components"

> Solution components should be modular and loosely coupled to facilitate efficient parallel development by small,
> effective teams, reduced architectural complexity and allowing developers to pick and choose the specific tools and
> capabilities they want to use based on their needs and what they are comfortable with.
We will focus on the language and compiler design itself.

An example of a very useful feature, that is strongly related but could be implemented separately instead,
is the ability to run the users code in a unit-testing context, without compilation+deployment first.
This would require implementing in Python some level of simulation of Algorand Nodes / AVM behaviour.

### "Secure by default"

> Include defaults, patterns and tooling that help developers write secure code and reduce the likelihood of security
> incidents in the Algorand ecosystem. This solution should help Algorand be the most secure Blockchain ecosystem.
Enforcing security (which is multi-faceted) at a compiler level is difficult, and is some cases impossible.
The best application of this principle here is to support auditing, which is important and nuanced enough to be
listed below as a separate principle.

### "Cohesive developer tool suite" + "Seamless onramp"

> Cohesive developer tool suite: Using AlgoKit should feel professional and cohesive, like it was designed to work
> together, for the developer; not against them. Developers are guided towards delivering end-to-end, high quality
> outcomes on MainNet so they and Algorand are more likely to be successful.
> Seamless onramp: New developers have a seamless experience to get started and they are guided into a pit of success
> with best practices, supported by great training collateral; you should be able to go from nothing to debugging code
> in 5 minutes.
These principles relate more to AlgoKit as a whole, so we can respect them by considering the impacts of our decisions
there more broadly.

## Abstraction without obfuscation

AlgoPy is a high level language (like Python), with support for things such as branching logic, operator precedence, etc.,
and not a set of "macros" for generating TEAL. As such, developers will not be able to directly influence specific TEAL
output, if this is desirable a language such as [Tealish](https://tealish.tinyman.org) is more appropriate.

Whilst this will abstract away certain aspects of the underlying TEAL language, there are certain AVM concerns
(such as op code budgets) that should not be abstracted away. That said, we should strive to generate code this is
cost-effective and unsurprising. Python mechanisms such as dynamic (runtime) dispatch, and also many of it's builtin
functions on types such as `str` that are taken for granted, would require large amounts of ops compared to the
Python code it represents.

## Support auditing

Auditing is a critical part of the security process for deploying smart contracts. We want to support this function,
and can do so in two ways:

1. By ensuring the same Python code as input generates identical output each time the compiler
is run regardless of the system it's running on. This is what might be termed [Output stability](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/articles/output_stability.md).
Ensuring a consistent output regardless of the system it's run on (assuming the same compiler version), means that
auditing the lower level (ie TEAL) code is possible.

2. Although auditing the TEAL code should be possible, being able to easily identify and relate it back to the higher level
code can make auditing the contract logic simpler and easier.

## Revolution, not evolution

This is a new and groundbreaking way of developing for Algorand, and not a continuation of the PyTEAL/Beaker approach.
By allowing developers to write procedural code, as opposed to constructing an expression tree,
we can (among other things) significantly reduce the barrier to entry for developing smart contracts for the Algorand platform.

Since the programming paradigm will be fundamentally different, providing a smooth migration experience from PyTEAL
to this new world is not an intended goal, and shouldn't be a factor in our decisions. For example, it is not a goal of
this project to produce a step-by-step "migrating from PyTEAL" document, as it is not a requirement for users to
switch to this new paradigm in the short to medium term - support for PyTEAL should continue in parallel.
4 changes: 2 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ packages = [
]

[tool.poetry.dependencies]
python = "^3.11"
python = "^3.12"
typing-extensions = "^4.8.0"
attrs = "^23.1.0"
structlog = "^23.2.0"
Expand Down Expand Up @@ -54,7 +54,7 @@ line-length = 99
force-exclude = "src/wyvern/(_typeshed|_vendor)"

[tool.mypy]
python_version = "3.11"
python_version = "3.12"
strict = true
untyped_calls_exclude = [
"algosdk",
Expand Down

0 comments on commit ed22176

Please sign in to comment.