Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Article Thumbnail

Hello Beaker

Hello Developer

If you’ve written and deployed a Smart Contract for Algorand using PyTeal before today, you’ve probably chewed a lot of glass to do so, well done!

Take heart; the Algorand team has been working hard to improve the development experience.

Before we dive into the improvements, let’s review some common things folks struggle with while developing.

Code Organization

When you start writing a contract, how do you structure the program logic? How do you handle inputs and outputs from the contract?

A very common pattern for writing the approval program in PyTeal is something like:

    def approval():
        #...
        return Cond(
            # Infer that this is create 
            [Txn.application_id() == Int(0), do_create_method()],
            # Check on complete manually
            [Txn.on_complete() == OnComplete.UpdateApplication, do_update()],
            # Use some const that you have to somehow communicate to the caller 
            # to route to the right method, then figure out how to parse the rest 
            # of the app args
            [Txn.application_args[0] == "do_the_thing", do_the_thing()],
            #...
        )

    # ...
    approval_teal = compileTeal(approval(), mode=Mode.Application, version=6)

This works, but is difficult to understand for a newcomer.

Interacting with the Application

To deploy an application on-chain, you submit an app create transaction. In this transaction, you specify the compiled TEAL programs, the application schema (“How many global uints do I need again?”), and extra program pages.

Calling an on-chain application involves crafting app call transactions with the appropriate routing and data arguments if not using the ABI.

Even when using the ABI, calling methods involves importing the contract description JSON and constructing an AtomicTransactionComposer, passing arguments as a list with no context about what they should be.

Managing State

Managing the application state schema is often done manually with constants for the keys and remembering what the type associated should be.

Creating the application requires you to know the number and type of each state value which you have no easy way to get automatically.

Debugging

Debugging can be a nightmare of trying to figure out an error message like assert failed: pc=XXX
PC Load Letter?

Testing

Testing contracts can be difficult and little guidance is provided. Often it requires rebuilding a lot of the front end infrastructure to test different inputs/outputs.

The devs did something

Now, let’s see how things have changed.

ABI

The ABI provides standards for encoding types, describing methods, and internally routing method calls to the appropriate logic.

With the ABI, we now have a standard way to both organize code and interact with the application.

More details on the ABI are available here

Atomic Transaction Composer

Using the Atomic Transaction Composer and the the ABI spec for your contract, you can easily compose atomic group transactions and have the arguments encoded and return values decoded for you!

Pyteal ABI

PyTeal now handles encoding/decoding of data types in a contract. The PyTeal Router class even provides a way to handle method routing logic, passing decoded types directly to a method, and provides the ABI contract spec.

For example, if you want a method that adds 1 to a uint8, you can write it thusly:

@router.method
def increment_my_number(input: abi.Uint8, *, output: abi.Uint8):
    return output.set(input.get() + Int(1))

EditorImages/2022/08/12 11:20/face_touch.png

So. Much. Nicer.

For more background see the blog post here

For detailed docs on PyTeal ABI see docs here

Source Maps

Mapping a pc returned from an error message has been made much easier. You can now compile TEAL with the sourcemap flag enabled. The resulting map comes back according to this spec and can be decoded with any of the SDKs using the new SourceMap object.

This means you can associate a pc directly to the source TEAL line, with all the familiar names and formatting you’re used to looking at.

Hello Beaker

Today we are sharing Beaker, a Smart Contract development framework meant to further improve the development experience.

Beaker takes advantage of the above improvements, allowing us to provide much more structure to applications.

Heads up though, it is still experimental.

Beaker Fire

Full Docs are here.

Let’s see how Beaker solves our problems.

Code Organization

Beaker provides a standard way to organize code by using a class to encapsulate functionality.

from beaker import Application, external

class MyApp(Application):
    @external
    def add(self, a: abi.Uint64, b: abi.Uint64, *, output: abi.Uint64):
        return output.set(a.get() + b.get())

This is a full application! It’s got an approval program, clear program, an implicitly empty state. The methods defined are provided in an ABI contract to export for other clients.

The @external decorator on the method exposes our defined method to callers and provides routing based on its method signature. The resulting method signature of this method is add(uint64,uint64)uint64.

The add method is a (mostly) valid PyTeal ABI Method. The exception that makes it mostly valid here, is that Beaker lets you pass self, allowing references to instance vars.

There is much more you can do including; access control, changing which OnComplete types may be used to call it, or marking it as a read-only method.

For more information, see the Decorator docs

Interacting with the application

Beaker provides an ApplicationClient to deal with common needs like creating/opting-in to/calling methods.

It uses your Application definition to provide context like the schema or the arguments required for the methods being called.

from beaker import sandbox, client

# get the first acct in the sandbox
acct = sandbox.get_accts().pop()

# create an app client
app_client = client.ApplicationClient(
    client=sandbox.get_algod_client(), 
    app=MyApp(), 
    signer=acct.signer
)

# deploy the app on-chain
app_client.create()

# call the method
result = app_client.call(MyApp.add, a=32, b=10)
print(result.return_value) # 42

# now go outside and touch some grass cuz you're done

For more see ApplicationClient docs

Managing state

Beaker allows you to declare typed state values as class variables.

from beaker import Application, ApplicationStateValue, external
class CounterApp(Application):
    counter = ApplicationStateValue(TealType.uint64)

    @external
    def incr_counter(self, incr_amt: abi.Uint64):
        self.counter.set(self.counter + incr_amt.get())

We can even inspect our application to see what its schema requirements are!

app = CounterApp() 
print(app.app_state.schema())

For more see State docs

Debugging

Beaker improves the pc=xxx error message using the source map endpoint during compilation and mapping the pc back to the source teal. The resulting LogicException allows you to see the exact source Teal line number with all the useful names of subroutines and any comments in the source teal.

Below is the result of a simple print of a LogicException telling me exactly where my program failed. More importantly it provides the context from the source TEAL showing me why it failed.

Txn WWVF5P2BXRNQDFFSGAGMCXJNDMZ224RJUGSMVPJVTBCVHEZMOMNA had error 'assert failed pc=883' at PC 883 and Source Line 579: 

        store 50
        store 49
        store 48
        store 47
        // correct asset a
        load 50
        txnas Assets
        bytec_0 // "a"
        app_global_get
        ==
        assert          <-- Error
        // correct asset b
        load 51
        txnas Assets
        bytec_1 // "b"
        app_global_get
        ==
        assert
        // correct pool token
        load 49

We’re also working on getting this mapping all the way back to the source PyTeal with this issue

Testing

Initially Beaker provides helpers for:

  • Retrieving and comparing account balances. Useful for ensuring the correct amounts of algos or tokens were transferred to or from relevant accounts.

  • Unit testing functionality by passing inputs and comparing to expected outputs. Useful for testing small, self contained behaviors.

For more information, see Testing docs.

More

There is a lot more not covered here, and a lot still to be done. Beaker needs your help to get better!

See the docs at https://beaker.algo.xyz

The code at https://github.com/algorand-devrel/beaker. Please feel free to file issues or PRs!

And for any questions, ping @barnji in the #beaker channel on the Algorand discord