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:
uintorintused instead ofuint256orint256,- 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.