Helper functions#
Wake testing framework provides a set of helper functions to make testing easier.
ABI encoding and decoding#
The Abi
class provides functions to encode and decode data according to the ABI specification.
Abi.encode#
Abi.encode
encodes a list of values given a list of types. It returns bytes
:
Abi.encode_packed#
Abi.encode_packed
encodes a list of values given a list of types. It returns bytes
:
Abi.encode_with_selector#
Abi.encode_with_selector
encodes a list of values and a selector given a list of types and the selector. It returns bytes
:
from wake.testing import Abi
from pytypes.contracts.Counter import Counter
Abi.encode_with_selector(Counter.setCount.selector, ['uint256'], [0xff])
Abi.encode_with_signature#
Abi.encode_with_signature
encodes a list of values and a selector given a list of types and a signature. It returns bytes
:
Warning
The signature string must conform to the ABI specification. The common mistakes are:
uint
orint
used instead ofuint256
orint256
,- return type specified,
- spaces in the signature string.
Abi.encode_call#
Abi.encode_call
encodes a list of values and a selector given a reference to a function. It returns bytes
:
from wake.testing import Abi
from pytypes.contracts.Counter import Counter
Abi.encode_call(Counter.setCount, [0xff])
Abi.decode#
Abi.decode
decodes a bytes
object given a list of types. It returns a list of values:
from wake.testing import Abi
Abi.decode(['uint8', 'address'],
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
Keccak-256#
The keccak256
function computes the Keccak-256 hash of a bytes
object:
Computing CREATE
and CREATE2
address#
In some cases, it may be useful to compute the address of a contract before it is deployed. Wake testing framework provides three functions to do so.
get_create_address#
get_create_address
computes the address of a contract deployed in a transaction or in a contract using the CREATE
opcode.
It accepts a deployer (Account
, Address
or a hex string address) and its nonce.
from wake.testing import Account, get_create_address
deployer = Account(1)
get_create_address(deployer, deployer.nonce)
get_create2_address_from_code#
get_create2_address_from_code
computes the address of a contract deployed using the CREATE2
opcode.
It accepts a deployer (Account
, Address
or a hex string address), a salt and the contract creation code.
from wake.testing import Account, get_create2_address_from_code
from wake.testing.fuzzing import random_bytes
from pytypes.contracts.Counter import Counter
get_create2_address_from_code(
Account(1),
random_bytes(32),
Counter.get_creation_code()
)
get_create2_address_from_hash#
get_create2_address_from_hash
computes the address of a contract deployed using the CREATE2
opcode.
It accepts a deployer (Account
, Address
or a hex string address), a salt and the hash of the contract creation code.
from wake.testing import Account, get_create2_address_from_hash, keccak256
from wake.testing.fuzzing import random_bytes
from pytypes.contracts.Counter import Counter
get_create2_address_from_hash(
Account(1),
random_bytes(32),
keccak256(Counter.get_creation_code())
)
Get logic contract from proxy#
get_logic_contract
returns the logic contract Account
from a proxy Account
.
If the input account is not a proxy, it returns the input account.
from wake.testing import Account, get_logic_contract
usdc_proxy = Account("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
usdc_logic = get_logic_contract(usdc_proxy)
Read & write storage variable#
read_storage_variable
and write_storage_variable
read and write storage variables of a contract.
They accept a contract Account
and a variable name.
The keys
argument is used to specify the array indexes, mapping keys and struct member names needed to access the variable.
Info
Reading and writing arrays is done using lists, while reading and writing structs is done using dictionaries.
Reading and writing whole mappings is not supported.
If the provided contract is a proxy, the variable definition is searched in the logic contract and the proxy storage is used.
This behavior can be overridden by setting the storage_layout_contract
argument.
In this case, the variable definition is searched in the provided storage_layout_contract
.
from wake.testing import Account, Address, read_storage_variable, write_storage_variable
usdc_proxy = Account("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
write_storage_variable(usdc_proxy, "balances", 1000, keys=[Address(1)])
assert read_storage_variable(usdc_proxy, "balances", keys=[Address(1)]) == 1000
ERC-20 mint and burn#
mint_erc20
and burn_erc20
mint and burn ERC-20 tokens. They detect the totalSupply
and balances
variables using heuristics and may not work for all contracts.
Optionally, balance_slot
and total_supply_slot
arguments can be used to specify the storage slot where the balance of the given account and the total supply are stored.
from wake.testing import Account, mint_erc20, burn_erc20
from pytypes.contracts.IERC20 import IERC20
usdc_proxy = IERC20("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
burn_erc20(usdc_proxy, Account(1), usdc_proxy.balanceOf(Account(1)))
mint_erc20(usdc_proxy, Account(1), 1000)
assert usdc_proxy.balanceOf(Account(1)) == 1000
Decorators#
on_revert#
on_revert
is a decorator that simplifies handling of revert exceptions. It accepts a callback function that will be called if the decorated function reverts.