Woke 2.0, development and testing framework for Solidity
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 *
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(): …
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])
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)
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()
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()
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.