The following documentation provides context, reasoning, and examples for methods and constants found in
|Expect this module to evolve (as it has already done).|
Table of Contents
To ease the readability of Cairo contracts, this project includes reusable constant variables like
UINT8_MAX, or EIP165 interface IDs such as
For more information on how interface ids are calculated, see the ERC165 documentation.
Cairo currently only provides support for short string literals (less than 32 characters). Note that short strings aren’t really strings, rather, they’re representations of Cairo field elements. The following methods provide a simple conversion to/from field elements.
Cairo’s native data type is a field element (felt). Felts equate to 252 bits which poses a problem regarding 256-bit integer integration. To resolve the bit discrepancy, Cairo represents 256-bit integers as a struct of two 128-bit integers. Further, the low bits precede the high bits e.g.
1 = (1, 0) 1 << 128 = (0, 1) (1 << 128) - 1 = (340282366920938463463374607431768211455, 0)
Converts a simple integer into a uint256-ish tuple.
to_uintshould be used in favor of
uintonly returns the low bits of the tuple.
Converts an integer into a uint256-ish tuple.
x = to_uint(340282366920938463463374607431768211456) print(x) # prints (0, 1)
Converts a uint256-ish tuple into an integer.
x = (0, 1) y = from_uint(x) print(y) # prints 340282366920938463463374607431768211456
Performs addition between two uint256-ish tuples and returns the sum as a uint256-ish tuple.
x = (0, 1) y = (1, 0) z = add_uint(x, y) print(z) # prints (1, 1)
Performs subtraction between two uint256-ish tuples and returns the difference as a uint256-ish tuple.
x = (0, 1) y = (1, 0) z = sub_uint(x, y) print(z) # prints (340282366920938463463374607431768211455, 0)
Performs multiplication between two uint256-ish tuples and returns the product as a uint256-ish tuple.
x = (0, 10) y = (2, 0) z = mul_uint(x, y) print(z) # prints (0, 20)
In order to abstract away some of the verbosity regarding test assertions on StarkNet transactions, this project includes the following helper methods:
An asynchronous wrapper method that executes a try-except pattern for transactions that should fail.
Note that this wrapper does not check for a StarkNet error code.
This allows for more flexibility in checking that a transaction simply failed.
If you wanted to check for an exact error code, you could use StarkNet’s error_codes module and implement additional logic to the
To successfully use this wrapper, the transaction method should be wrapped with
await should precede the wrapper itself like this:
await assert_revert(signer.send_transaction( account, contract.contract_address, 'foo', [ recipient, *token ]) )
This wrapper also includes the option to check that an error message was included in the reversion.
To check that the reversion sends the correct error message, add the
reverted_with keyword argument outside of the actual transaction (but still inside the wrapper) like this:
await assert_revert(signer.send_transaction( account, contract.contract_address, 'foo', [ recipient, *token ]), reverted_with="insert error message here" )
An extension of
assert_revert that asserts an entry point error occurs with the given
This assertion is especially useful in checking proxy/implementation contracts.
await assert_revert_entry_point( signer.send_transaction( account, contract.contract_address, 'nonexistent_selector',  ), invalid_selector='nonexistent_selector' )
A helper method that checks a transaction receipt for the contract emitting the event (
from_address), the emitted event itself (
name), and the arguments emitted (
# capture the tx receipt tx_exec_info = await signer.send_transaction( account, contract.contract_address, 'foo', [ recipient, *token ]) # insert arguments to assert assert_event_emitted( tx_exec_info, from_address=contract.contract_address, name='Foo_emitted', data=[ account.contract_address, recipient, *token ] )
Memoizing functions allow for quicker and computationally cheaper calculations which is immensely beneficial while testing smart contracts.
A helper method that returns the contract class from the contract’s name. To capture the contract class, simply add the contract’s name as an argument like this:
contract_class = get_contract_class('ContractName')
If multiple contracts exist with the same name, then the contract’s path must be passed along with the
is_path flag instead of the name.
To pass the contract’s path:
contract_class = get_contract_class('path/to/Contract.cairo', is_path=True)
A helper method that returns the cached state of a given contract.
It’s recommended to first deploy all the relevant contracts before caching the state.
The requisite contracts in the testing module should each be instantiated with
cached_contract in a fixture after the state has been copied.
The memoization pattern with
cached_contract should look something like this:
# get contract classes @pytest.fixture(scope='module') def contract_classes(): foo_cls = get_contract_class('Foo') return foo_cls # deploy contracts @pytest.fixture(scope='module') async def foo_init(contract_classes): foo_cls = contract_classes starknet = await Starknet.empty() foo = await starknet.deploy( contract_class=foo_cls, constructor_calldata= ) return starknet.state, foo # return state and all deployed contracts # memoization @pytest.fixture def foo_factory(contract_classes, foo_init): foo_cls = contract_classes # contract classes state, foo = foo_init # state and deployed contracts _state = state.copy() # copy the state cached_foo = cached_contract(_state, foo_cls, foo) # cache contracts return cached_foo # return cached contracts
State class provides a wrapper for initializing the StarkNet state which acts as a helper for the
Account class. This wrapper allows
Account deployments to share the same initialized state without explicitly passing the instantiated StarkNet state to
Account. Initializing the state should look like this:
from utils import State starknet = await State.init()
Account class abstracts away most of the boilerplate for deploying accounts. Instantiating accounts with this class requires the StarkNet state to be instantiated first with the
State.init() method like this:
from utils import State, Account starknet = await State.init() account1 = await Account.deploy(public_key) account2 = await Account.deploy(public_key)
The Account class also provides access to the account contract class which is useful for following the Memoization pattern. To fetch the account contract class:
fetch_class = Account.get_class
MockSigner is used to perform transactions with an instance of Nile’s Signer on a given Account, crafting the transaction and managing nonces.
Signer instance manages signatures and is leveraged by
MockSigner to operate with the Account contract’s
See MockSigner utility for more information.