Woke 2.0, development and testing framework for Solidity

Aleksandra Yudina
Ackee Blockchain
Published in
4 min readMar 28, 2023

With Woke 2.0, we introduced an updated version of our Python-based development and testing framework.

Let’s look closely and find out how it differs from other Python-based testing frameworks and what are Woke’s advantages.

PYTYPES

Woke testing framework builds on the so-called pytypes. These are automatically generated Python files representing Solidity contracts, structs, enums, etc. in the Python world. Files in the pytypes directory are organized in such a way so that it reflected the directory structure of the original Solidity sources. pytypes not only provide auto-completion and type hints support but also work as standalone objects that do not need any external files (Solidity source files, for example). All necessary information is included in pytypes.

To generate pytypes for a project in a current working directory, one can you the woke init pytypes command. An optional -w or --watch option launches the pytypes generator in watch mode. Solidity source files are watched for changes and pytypes are automatically re-generated.

Woke testing framework was inspired by Brownie, thus the way it interacts with the contracts may be familiar to Brownie users. However, as Woke learned from Brownie’s mistakes, there are some crucial differences and improvements.

GETTING STARTED WITH WOKE

First, a contract must be imported from pytypes. Importing contracts from different namespaces (modules) is essential as it helps to avoid name clashes.

from pytypes.contracts.Counter import Counter

from woke.testing import *

view rawwoke2.py hosted with

by GitHub

default_chain can be either connected to an already running dev chain or launch a new dev chain.

from woke.testing import *# launch a new dev chain@default_chain.connect()# or connect to an existing one using an URI# @default_chain.connect(“ws://localhost:8545”)def test_counter(): …

view rawwoke3.py hosted with

by GitHub

With the default configuration, Woke uses Anvil as the development chain. Anvil is a part of the Foundry project. It is highly recommended to keep Anvil up-to-date: Woke development team co-operates with Anvil developers to ensure a seamless experience when writing tests.

With default_chain connected to a dev chain, it is easy to deploy the first contract using Woke testing framework.

from woke.testing import *from pytypes.contracts.Counter import Counter@default_chain.connect()def test_counter(): counter = Counter.deploy(from_=default_chain.accounts[0])

view rawwoke4.py hosted with

by GitHub

MAIN DIFFERENCES FROM BROWNIE

The previous example shows another difference from Brownie. Any additional data that modify a transaction or call are passed using keyword arguments instead of a dictionary. By default, pure and view Solidity functions (including public state variable getters) are called using eth_call, i.e. a transaction is not sent. The request_type keyword can override this.

Another significant difference from Brownie is that pytypes function calls that send a transaction return the function return value instead of a transaction object. Setting return_tx=True in a function call or even generating pytypes with return_tx implicitly set: woke init pytypes --return-tx can override this.

from woke.testing import *from pytypes.contracts.Counter import Counter@default_chain.connect()def test_counter(): # set the default account for sending transactions # the default account for calls is already set to default_chain.accounts[0] default_chain.default_tx_account = default_chain.accounts[0] counter = Counter.deploy() # performs a call print(counter.count()) # sends a transaction print(counter.count(request_type=”tx”)) # sends a transaction and returns a return value counter.increment() # sends a transaction and returns a transaction object tx = counter.increment(return_tx=True) print(tx.tx_hash)

view rawwoke5.py hosted with

by GitHub

This example may show yet another advantage of Woke testing framework: it was designed to be very fast. There are a few things to keep in mind in order to achieve maximum performance:

  • Anvil should be used whenever possible
  • Transaction events should be accessed only when necessary; working with tx.events may be slow in some cases
  • Transactions should be mainly sent from pre-generated accounts (default_chain.accounts); sending a transaction from any other account is possible but at the expense of poorer performance.

Thanks to pytypes, transaction events can be worked with using native Python objects, dataclasses. events is the property of a transaction object received either by setting return_tx=True or by defining tx_callback.

from woke.testing import *from pytypes.contracts.Counter import Counterdef tx_callback(tx: TransactionAbc): print(tx.events)@default_chain.connect()def test_counter(): default_chain.default_tx_account = default_chain.accounts[0] default_chain.tx_callback = tx_callback c = Counter.deploy() c.increment()

view rawwoke6.py hosted with

by GitHub

tx_callback is automatically called for transactions that do not have return_tx=True set. User-defined errors are also generated in the form of dataclasses. Additionally, there are two internal types of errors: Error(str) and Panic(int). Errors are automatically raised as exceptions with return_tx set to False. There are helper context managers, must_revert and may_revert, to make handling exceptions easier.

from woke.testing import *from pytypes.contracts.Counter import Counter@default_chain.connect()def test_counter(): default_chain.default_tx_account = default_chain.accounts[0] c = Counter.deploy() with must_revert(Panic(PanicCodeEnum.UNDERFLOW_OVERFLOW)): c.decrement()

view rawwoke7.py hosted with

by GitHub

The mentioned above covers the key differences between Woke and Brownie. Be sure to check out our documentation and follow our Twitter to stay updated.

--

--