Accounts and addresses#
Woke testing framework strictly distinguishes between accounts and addresses.
However, in most cases, API functions accept both
Address is a 20-byte value encoded as a hex string. It can be constructed from
a hex string or an integer:
The hex string does not have to be EIP-55 compliant.
Addresses can be compared with each other:
Account is an
Address bound to a specific
Chain. It can be constructed from
Address, a hex string or an integer. Optionally, a chain can be specified, otherwise
default_chain global object is used:
from woke.testing import Account, Chain, default_chain other_chain = Chain() assert Account(0) == Account(0, default_chain) assert Account(0) != Account(0, other_chain)
Account instances cannot be compared with each other.
Account instances belonging to different
chains cannot be compared using the
Using accounts belonging to different chains
To save users from accidentally using accounts belonging to different chains, Woke testing framework
does not accept
Account instances belonging to different chains in most API functions. To overcome
this limitation, it is possible to use the
address property of an
Account instances can be imported from a private key:
From a mnemonic:
Or from an alias (see Managing accounts with private keys):
It is also possible to create a new account with a random private key:
In all of the above cases, a private key is stored together with the account and can be used to sign transactions or messages.
Account instances can be used to sign messages. This is only possible if the account has a known private key.
The private key must be imported using one of the methods described in the previous section or must be owned by
the client (the account must be present in
Signing raw messages#
account.sign(message) it is possible to sign any message in the form of bytes:
from woke.testing import Account account = Account.from_mnemonic(" ".join(["test"] * 11 + ["junk"])) signature = account.sign(b"Hello, world!")
The message is signed according to the EIP-191 standard (version
Signing structured messages#
account.sign_structured(message) it is possible to sign structured messages.
from woke.testing import * from dataclasses import dataclass @dataclass class Transfer: sender: Address recipient: Address amount: uint256 account = Account.from_mnemonic(" ".join(["test"] * 11 + ["junk"])) signature = account.sign_structured( Transfer( sender=account.address, recipient=Address(1), amount=10, ), domain=Eip712Domain( name="Test", chainId=default_chain.chain_id, ) )
See EIP-712 for more information.
Signing message hash#
While it is not recommended to sign message hashes directly, it is sometimes necessary.
To sign a message hash, use
from woke.testing import * account = Account.from_mnemonic(" ".join(["test"] * 11 + ["junk"])) signature = account.sign_hash(keccak256(b"Hello, world!"))
account.sign_hash is not available for accounts owned by the client.
Always sign a message hash only if you know the original message.
Account instances can be assigned labels. Labels override the default string representation
of the account:
Setting the label to
None removes the label.
Account instances have the following properties:
||balance of the account in Wei|
||code of the account|
||string label of the account|
||nonce of the account|
||private key of the account (if known)|
private_key, all properties can be assigned to.
nonce can only be incremented.
Low-level calls and transactions#
Account instance has
access_list methods that can be used to perform arbitrary
requests (see Interacting with contracts).
from woke.testing import * @default_chain.connect() def test_accounts(): alice = default_chain.accounts bob = default_chain.accounts alice.balance = 100 bob.balance = 0 bob.transact(value=10, from_=alice) assert alice.balance == 90 assert bob.balance == 10
The previous example shows how to transfer Wei from one account to another.
Contract accounts are accounts that have non-empty code. Everything that applies to
also applies to contract accounts. However, contract accounts have additional methods:
get_creation_code- returns the code used to deploy the contract, may require addresses of libraries needed by the contract,
deploy- deploys the contract, requires equivalent arguments as the constructor of the contract in Solidity,
- other contract-specific methods generated in
pytypes, including getters for public state variables.
from pytypes.contracts.Counter import Counter assert len(Counter.get_creation_code()) > 0 print(Counter.setCount.selector.hex())
Every method of a contract generated in
pytypes has a
Constructing contracts from an address
The ability to construct a contract from an address (and an optional
Chain instance) can be very useful
when interacting with contracts through proxies:
from woke.testing import * from pytypes.contracts.Counter import Counter from pytypes.openzeppelin.contracts.proxy.ERC1967.ERC1967Proxy import ERC1967Proxy @default_chain.connect() def test_proxy(): default_chain.default_tx_account = default_chain.accounts impl = Counter.deploy() proxy = ERC1967Proxy.deploy(impl, b"") # behave as if Counter was deployed at proxy.address counter = Counter(proxy.address) counter.increment() assert counter.count() == 1