diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index ade19aa85..6eb123c17 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -43,6 +43,7 @@ *** xref:/api/upgrades.adoc[API Reference] ** xref:/api/utilities.adoc[Utilities] +** xref:/api/testing.adoc[Test Utilities] * xref:backwards-compatibility.adoc[Backwards Compatibility] * xref:contracts::index.adoc[Contracts for Solidity] diff --git a/docs/modules/ROOT/pages/api/testing.adoc b/docs/modules/ROOT/pages/api/testing.adoc new file mode 100644 index 000000000..7d7289324 --- /dev/null +++ b/docs/modules/ROOT/pages/api/testing.adoc @@ -0,0 +1,298 @@ += Testing + +:stark: https://docs.starknet.io/architecture-and-concepts/cryptography/stark-curve/[Stark] +:secp256k1: https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/secp256k1.cairo[Secp256k1] + +This module provides various helper functions for declaring, deploying, +and testing Smart Contracts using `snforge` toolchain from Starknet Foundry. + +```cairo +use openzeppelin_testing; +``` + +The module isn't part of the `openzeppelin` package and to be accessible has to +be added as a separate dependency in `Scarb.toml`: + +``` +[dev-dependencies] +openzeppelin_testing = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.15.0-rc.0" } +``` + +== Test Utilities + +[.contract] +[[testing-common]] +=== `++common++` + +A module providing common test helpers. + +```cairo +use openzeppelin_testing::common; +``` + +[.contract-index] +.Members +-- +.Functions +* xref:#testing-common-panic_data_to_byte_array[`++panic_data_to_byte_array(panic_data)++`] +* xref:#testing-common-to_base_16_string[`++to_base_16_string(value)++`] +* xref:#testing-common-assert_entrypoint_not_found_error[`++assert_entrypoint_not_found_error(result, selector, contract_address)++`] + +.Traits +* xref:#testing-common-IntoBase16StringTrait[`++IntoBase16StringTrait++`] +-- + +[#testing-common-Functions] +==== Functions + +[.contract-item] +[[testing-common-panic_data_to_byte_array]] +===== `[.contract-item-name]#++panic_data_to_byte_array++#++(panic_data: Array) → ByteArray++` [.item-kind]#function# + +Converts panic data into a string (`ByteArray`). + +`panic_data` is expected to be a valid serialized `ByteArray` with an extra `felt252` at the beginning, which is the BYTE_ARRAY_MAGIC. + +[.contract-item] +[[testing-common-to_base_16_string]] +===== `[.contract-item-name]#++to_base_16_string++#++(value: felt252) → ByteArray++` [.item-kind]#function# + +Converts a `felt252` to a `base16` string padded to 66 characters (including the `0x` prefix). + +[.contract-item] +[[testing-common-assert_entrypoint_not_found_error]] +==== `[.contract-item-name]#++assert_entrypoint_not_found_error++#>(result: SyscallResult, selector: felt252, contract_address: ContractAddress)` [.item-kind]#function# + +Asserts that the syscall result of a call failed with an "Entrypoint not found" error, +following the Starknet Foundry emitted error format. + +[#testing-common-Traits] +==== Traits + +[.contract-item] +[[testing-common-IntoBase16StringTrait]] +==== `[.contract-item-name]#++IntoBase16StringTrait++#` [.item-kind]#trait# + +A helper trait that enables a value to be represented as a `base16` string padded to 66 characters +(including the `0x` prefix). The type of the value must implement `Into` to be +convertible to `felt252`. + +Usage example: + +```cairo +use openzeppelin_testing::common::IntoBase16String; + +let expected_panic_message = format!( + "Entry point selector {} not found in contract {}", + selector.into_base_16_string(), + contract_address.into_base_16_string() +); +``` + +[.contract] +[[testing-deployment]] +=== `++deployment++` + +```cairo +use openzeppelin_testing::deployment; +``` + +A module containing utilities that simplify declaring and deploying contracts using the `snforge` toolchain. + +[.contract-index] +.Members +-- +.Functions +* xref:#testing-deployment-declare_class[`++declare_class(contract_name)++`] +* xref:#testing-deployment-deploy[`++deploy(contract_class, calldata)++`] +* xref:#testing-deployment-deploy_at[`++deploy_at(contract_class, contract_address, calldata)++`] +* xref:#testing-deployment-deploy_another_at[`++deploy_another_at(existing, target_address, calldata)++`] +* xref:#testing-deployment-declare_and_deploy[`++declare_and_deploy(contract_name, calldata)++`] +* xref:#testing-deployment-declare_and_deploy_at[`++declare_and_deploy_at(contract_name, target_address, calldata)++`] +-- + +[#testing-deployment-Functions] +==== Functions + +[.contract-item] +[[testing-deployment-declare_class]] +==== `[.contract-item-name]#++declare_class++#++(contract_name: ByteArray) → ContractClass++` [.item-kind]#function# + +Declares a contract with a `snforge` `declare` call and unwraps the result. + +[.contract-item] +[[testing-deployment-deploy]] +==== `[.contract-item-name]#++deploy++#++(contract_class: ContractClass, calldata: Array) → ContractAddress++` [.item-kind]#function# + +Deploys an instance of a contract and unwraps the result. + +[.contract-item] +[[testing-deployment-deploy_at]] +==== `[.contract-item-name]#++deploy_at++#++(contract_class: ContractClass, target_address: ContractAddress, calldata: Array)++` [.item-kind]#function# + +Deploys an instance of a contract at a given address. + +[.contract-item] +[[testing-deployment-deploy_another_at]] +==== `[.contract-item-name]#++deploy_another_at++#++(existing: ContractAddress, target_address: ContractAddress, calldata: Array)++` [.item-kind]#function# + +Deploys a contract using the class hash from another already-deployed contract. + +Note that currently, `snforge` does not support redeclaring a contract class. Consequently, +there is no direct method to deploy a second instance of a contract if neither its `ContractClass` +nor its `class_hash` is available in the context. This helper function provides a solution by retrieving +the class hash from an existing contract and using it to facilitate the deployment. + +```cairo +use openzeppelin_testing::deploy_another_at; + +let alice_address = setup_account(array!['ALICE_PUBKEY']); +let bob_address = contract_address_const::<'BOB'>(); +deploy_another_at(alice_address, bob_address, array!['BOB_PUBKEY']); +``` + +[.contract-item] +[[testing-deployment-declare_and_deploy]] +==== `[.contract-item-name]#++declare_and_deploy++#++(contract_name: ByteArray, calldata: Array) → ContractAddress++` [.item-kind]#function# + +Combines the declaration of a class and the deployment of a contract into one function call. + +[.contract-item] +[[testing-deployment-declare_and_deploy_at]] +==== `[.contract-item-name]#++declare_and_deploy_at++#++(contract_name: ByteArray, target_address: ContractAddress, calldata: Array)++` [.item-kind]#function# + +Combines the declaration of a class and the deployment of a contract at the given address into one function call. + +[.contract] +[[testing-events]] +=== `++events++` + +```cairo +use openzeppelin_testing::events; +use openzeppelin_testing::events::EventSpyExt; +``` + +A module offering an extended set of functions for handling emitted events, enhancing the default +event utilities provided by `snforge`. These functions are accessible via the `EventSpyExt` +trait implemented on the `EventSpy` struct. + +[.contract-index] +.Members +-- +.Functions +* xref:#testing-events-assert_only_event[`++assert_only_event(self, from_address, event)++`] +* xref:#testing-events-assert_emitted_single[`++assert_emitted_single(self, from_address, expected_event)++`] +* xref:#testing-events-drop_event[`++drop_event(self)++`] +* xref:#testing-events-drop_n_events[`++drop_n_events(self, number_to_drop)++`] +* xref:#testing-events-drop_all_events[`++drop_all_events(self)++`] +* xref:#testing-events-assert_no_events_left[`++assert_no_events_left(self)++`] +* xref:#testing-events-assert_no_events_left_from[`++assert_no_events_left_from(self, from_address)++`] +* xref:#testing-events-count_events_from[`++count_events_from(self, from_address)++`] +-- + +[#testing-events-Functions] +==== Functions + +[.contract-item] +[[testing-events-assert_only_event]] +==== `[.contract-item-name]#++assert_only_event++#++, +Drop>(ref self: EventSpy, from_address: ContractAddress, expected_event: T)++` [.item-kind]#function# + +Ensures that `from_address` has emitted only the `expected_event` and no additional events. + +[.contract-item] +[[testing-events-assert_emitted_single]] +==== `[.contract-item-name]#++assert_emitted_single++#++, +Drop>(ref self: EventSpy, from_address: ContractAddress, expected_event: T)++` [.item-kind]#function# + +Ensures that `from_address` has emitted the `expected_event`. + +[.contract-item] +[[testing-events-drop_event]] +==== `[.contract-item-name]#++drop_event++#++(ref self: EventSpy)++` [.item-kind]#function# + +Removes a single event from the queue. If the queue is empty, the function will panic. + +[.contract-item] +[[testing-events-drop_n_events]] +==== `[.contract-item-name]#++drop_n_events++#++(ref self: EventSpy, number_to_drop: u32)++` [.item-kind]#function# + +Removes `number_to_drop` events from the queue. If the queue is empty, the function will panic. + +[.contract-item] +[[testing-events-drop_all_events]] +==== `[.contract-item-name]#++drop_all_events++#++(ref self: EventSpy)++` [.item-kind]#function# + +Removes all events remaining on the queue. If the queue is empty already, the function will do nothing. + +[.contract-item] +[[testing-events-assert_no_events_left]] +==== `[.contract-item-name]#++assert_no_events_left++#++(ref self: EventSpy)++` [.item-kind]#function# + +Ensures that there are no events remaining on the queue. + +[.contract-item] +[[testing-events-assert_no_events_left_from]] +==== `[.contract-item-name]#++assert_no_events_left_from++#++(ref self: EventSpy, from_address: ContractAddress)++` [.item-kind]#function# + +Ensures that there are no events emitted from the given address remaining on the queue. + +[.contract-item] +[[testing-events-count_events_from]] +==== `[.contract-item-name]#++count_events_from++#++(ref self: EventSpy, from_address: ContractAddress) → u32++` [.item-kind]#function# + +Counts the number of remaining events emitted from the given address. + +[.contract] +[[testing-signing]] +=== `++signing++` + +```cairo +use openzeppelin_testing::signing; +``` + +A module offering utility functions for easier management of key pairs and signatures. + +[.contract-index] +.Members +-- +.Functions +* xref:#testing-signing-get_stark_keys_from[`++get_stark_keys_from(private_key)++`] +* xref:#testing-signing-get_secp256k1_keys_from[`++get_secp256k1_keys_from(private_key)++`] + +.Traits +* xref:#testing-signing-SerializedSigning[`++SerializedSigning++`] +-- + +[#testing-signing-Functions] +==== Functions + +[.contract-item] +[[testing-signing-get_stark_keys_from]] +==== `[.contract-item-name]#++get_stark_keys_from++#++(private_key: felt252) → StarkKeyPair++` [.item-kind]#function# + +Builds a {stark} key pair from a private key represented by a `felt252` value. + +[.contract-item] +[[testing-signing-get_secp256k1_keys_from]] +==== `[.contract-item-name]#++get_secp256k1_keys_from++#++(private_key: u256) → Secp256k1KeyPair++` [.item-kind]#function# + +Builds a {secp256k1} key pair from a private key represented by a `u256` value. + +[#testing-signing-Traits] +==== Traits + +[.contract-item] +[[testing-signing-SerializedSigning]] +==== `[.contract-item-name]#++SerializedSigning++#` [.item-kind]#trait# + +A helper trait that facilitates signing and converting the result signature into a serialized format. + +Usage example: + +```cairo +use openzeppelin_testing::signing::{ + StarkKeyPair, get_stark_keys_from, StarkSerializedSigning +}; + +let key_pair = get_stark_keys_from('SECRET_KEY'); +let serialized_signature = key_pair.serialized_sign('TX_HASH'); +``` \ No newline at end of file diff --git a/docs/modules/ROOT/pages/api/utilities.adoc b/docs/modules/ROOT/pages/api/utilities.adoc index 56c28ef5f..38c870b56 100644 --- a/docs/modules/ROOT/pages/api/utilities.adoc +++ b/docs/modules/ROOT/pages/api/utilities.adoc @@ -2,8 +2,7 @@ :deploy_syscall: link:https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/#deploy[deploy_syscall] -The following documentation provides reasoning and examples for functions and constants found in `openzeppelin::utils` -and `use openzeppelin::utils::test_utils;`. +The following documentation provides reasoning and examples for functions and constants found in `openzeppelin::utils`. CAUTION: Expect this module to evolve (as it has already done). @@ -379,135 +378,3 @@ Supports on-chain generation of message hashes compliant with {snip12}. NOTE: For a full walkthrough on how to use this module, see the xref:/guides/snip12.adoc[SNIP12 and Typed Messages] guide. - -== Test utilities - -[.contract] -[[testutils]] -=== `++utils++` - -```cairo -use openzeppelin::utils::test_utils; -``` - -Module containing utilities for testing the library. - -[.contract-index] -.Members --- -.Functions -* xref:#testutils-deploy[`++deploy(contract_class_hash, calldata)++`] -* xref:#testutils-deploy_with_salt[`++deploy_with_salt(contract_class_hash, calldata, salt)++`] -* xref:#testutils-pop_log[`++pop_log(address)++`] -* xref:#testutils-assert_indexed_keys[`++assert_indexed_keys(event, expected_keys)++`] -* xref:#testutils-assert_no_events_left[`++assert_no_events_left(address)++`] -* xref:#testutils-drop_event[`++drop_event(address)++`] -* xref:#testutils-drop_events[`++drop_events(address, n_events)++`] --- - -[#testutils-Functions] -==== Functions - -[.contract-item] -[[testutils-deploy]] -==== `[.contract-item-name]#++deploy++#++(contract_class_hash: felt252, calldata: Array) → ContractAddress++` [.item-kind]#function# - -Uses the `{deploy_syscall}` to deploy an instance of the contract given the class hash and the calldata. - -The `contract_address_salt` is always set to zero, and `deploy_from_zero` is set to false. - -Usage example: - -```cairo -use openzeppelin::presets::AccountUpgradeable; -use openzeppelin::utils::test_utils; -use starknet::ContractAddress; - -const PUBKEY: felt252 = 'PUBKEY'; - -fn deploy_test_contract() -> ContractAddress { - let calldata = array![PUBKEY]; - test_utils::deploy(AccountUpgradeable::TEST_CLASS_HASH, calldata) -} -``` - -[.contract-item] -[[testutils-deploy_with_salt]] -==== `[.contract-item-name]#++deploy_with_salt++#++(contract_class_hash: felt252, calldata: Array, salt: felt252) → ContractAddress++` [.item-kind]#function# - -:deploy: xref:testutils-deploy[utils::deploy] - -Same as {deploy} except this function accepts a `salt` argument. -This utility is useful when tests require multiple deployed instances of the same contract. - -[.contract-item] -[[testutils-pop_log]] -==== `[.contract-item-name]#++pop_log++#++(address: ContractAddress) → Option++` [.item-kind]#function# - -Pops the earliest unpopped logged event for the contract as the requested type -and checks that there's no more keys or data left on the event, preventing unaccounted params. - -Required traits for `T`: - -- `Drop` -- `starknet::Event` - -Requirements: - -- No extra data or keys are left on the raw event after deserialization. - -Usage example: - -```cairo -use openzeppelin::utils::test_utils; -use openzeppelin::token::erc20::ERC20Component; -use openzeppelin::token::erc20::ERC20Component::Transfer; -use starknet::ContractAddress; - -fn assert_emitted_event( - target: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 -) { - let event = test_utils::pop_log::(target).unwrap(); - let expected = ERC20Component::Event::Transfer(Transfer { from, to, value }); - assert!(event == expected); -} -``` - -[.contract-item] -[[testutils-assert_indexed_keys]] -==== `[.contract-item-name]#++assert_indexed_keys++#(event: T, expected_keys: Span)` [.item-kind]#function# - -Asserts that `expected_keys` exactly matches the indexed keys from `event`. - -`expected_keys` must include all indexed event keys for `event` in the order -that they're defined. - -NOTE: If the event is not flattened, the first key will be the event member name -e.g. `selector!("EnumMemberName")`. - -Required traits for `T`: - -- `Drop` -- `starknet::Event` - -[.contract-item] -[[testutils-assert_no_events_left]] -==== `[.contract-item-name]#++assert_no_events_left++#++(address: ContractAddress)++` [.item-kind]#function# - -Asserts that there are no more events left in the queue for the given address. - -[.contract-item] -[[testutils-drop_event]] -==== `[.contract-item-name]#++drop_event++#++(address: ContractAddress)++` [.item-kind]#function# - -Removes an event from the queue for the given address. - -If the queue is empty, this function won't do anything. - -[.contract-item] -[[testutils-drop_events]] -==== `[.contract-item-name]#++drop_events++#++(address: ContractAddress, n_events: felt252)++` [.item-kind]#function# - -Removes `n_events` from the queue for the given address. - -If the queue is empty, this function won't do anything. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 1a2bc7e08..b1a5e44d9 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -5,7 +5,8 @@ = Contracts for Cairo *A library for secure smart contract development* written in Cairo for {starknet}. This library consists of a set of xref:components.adoc[reusable components] to build custom smart contracts, as well as -ready-to-deploy xref:presets.adoc[presets]. You can also find other xref:/api/utilities.adoc[utilities] including xref:interfaces.adoc[interfaces and dispatchers]. +ready-to-deploy xref:presets.adoc[presets]. You can also find other xref:/api/utilities.adoc[utilities] including xref:interfaces.adoc[interfaces and dispatchers] and xref:/api/testing.adoc[test utilities] +that facilitate testing with Starknet Foundry. WARNING: This repo contains highly experimental code. Expect rapid iteration. *Use at your own risk.* diff --git a/packages/account/src/tests/test_dual_account.cairo b/packages/account/src/tests/test_dual_account.cairo index 1aa6dc619..48d552e3c 100644 --- a/packages/account/src/tests/test_dual_account.cairo +++ b/packages/account/src/tests/test_dual_account.cairo @@ -6,7 +6,7 @@ use openzeppelin_test_common::account::{SIGNED_TX_DATA, get_accept_ownership_sig use openzeppelin_testing as utils; use openzeppelin_testing::constants::TRANSACTION_HASH; use openzeppelin_testing::constants::stark::{KEY_PAIR, KEY_PAIR_2}; -use openzeppelin_testing::signing::{StarkKeyPair, StarkKeyPairExt}; +use openzeppelin_testing::signing::{StarkKeyPair, StarkSerializedSigning}; use snforge_std::{declare, start_cheat_caller_address}; // diff --git a/packages/account/src/tests/test_dual_eth_account.cairo b/packages/account/src/tests/test_dual_eth_account.cairo index 3cf5d8140..6169cdaaa 100644 --- a/packages/account/src/tests/test_dual_eth_account.cairo +++ b/packages/account/src/tests/test_dual_eth_account.cairo @@ -8,7 +8,7 @@ use openzeppelin_test_common::eth_account::get_accept_ownership_signature; use openzeppelin_testing as utils; use openzeppelin_testing::constants::secp256k1::KEY_PAIR; use openzeppelin_testing::constants::{ETH_PUBKEY, NEW_ETH_PUBKEY, TRANSACTION_HASH}; -use openzeppelin_testing::signing::Secp256k1KeyPairExt; +use openzeppelin_testing::signing::Secp256k1SerializedSigning; use openzeppelin_utils::serde::SerializedAppend; use snforge_std::start_cheat_caller_address; diff --git a/packages/test_common/src/erc1155.cairo b/packages/test_common/src/erc1155.cairo index c8adab2a4..75350ec22 100644 --- a/packages/test_common/src/erc1155.cairo +++ b/packages/test_common/src/erc1155.cairo @@ -6,7 +6,6 @@ use openzeppelin_token::erc1155::ERC1155Component; use snforge_std::EventSpy; use starknet::ContractAddress; - pub fn setup_receiver() -> ContractAddress { utils::declare_and_deploy("SnakeERC1155ReceiverMock", array![]) } diff --git a/packages/test_common/src/eth_account.cairo b/packages/test_common/src/eth_account.cairo index 4be1af249..857581b47 100644 --- a/packages/test_common/src/eth_account.cairo +++ b/packages/test_common/src/eth_account.cairo @@ -8,7 +8,7 @@ use openzeppelin_account::interface::EthPublicKey; use openzeppelin_account::utils::signature::EthSignature; use openzeppelin_testing::constants::TRANSACTION_HASH; use openzeppelin_testing::events::EventSpyExt; -use openzeppelin_testing::signing::{Secp256k1KeyPair, Secp256k1KeyPairExt}; +use openzeppelin_testing::signing::{Secp256k1KeyPair, Secp256k1SerializedSigning}; use snforge_std::EventSpy; use snforge_std::signature::secp256k1_curve::Secp256k1CurveSignerImpl; use starknet::{ContractAddress, SyscallResultTrait}; diff --git a/packages/testing/src/common.cairo b/packages/testing/src/common.cairo index f453a32fb..dbd61b297 100644 --- a/packages/testing/src/common.cairo +++ b/packages/testing/src/common.cairo @@ -17,7 +17,7 @@ pub fn panic_data_to_byte_array(panic_data: Array) -> ByteArray { } } -/// Converts a felt252 to a base 16 string padded to 66 characters including the `0x` prefix. +/// Converts a `felt252` to a `base16` string padded to 66 characters including the `0x` prefix. pub fn to_base_16_string(value: felt252) -> ByteArray { let mut string = value.format_as_byte_array(16); let mut padding = 64 - string.len(); @@ -29,6 +29,8 @@ pub fn to_base_16_string(value: felt252) -> ByteArray { format!("0x{}", string) } +/// A helper trait that enables any value that can be converted to `felt252` to be represented +/// as a `base16` string padded to 66 characters (including the `0x` prefix). #[generate_trait] pub impl IntoBase16String> of IntoBase16StringTrait { fn into_base_16_string(self: T) -> ByteArray { @@ -37,7 +39,7 @@ pub impl IntoBase16String> of IntoBase16StringTrait { } /// Asserts that the syscall result of a call failed with an "Entrypoint not found" error, -/// following the starknet foundry emitted error format. +/// following the Starknet Foundry emitted error format. pub fn assert_entrypoint_not_found_error>( result: SyscallResult, selector: felt252, contract_address: ContractAddress ) { diff --git a/packages/testing/src/constants.cairo b/packages/testing/src/constants.cairo index 1d02e01d1..e6447b9d5 100644 --- a/packages/testing/src/constants.cairo +++ b/packages/testing/src/constants.cairo @@ -1,14 +1,11 @@ -use starknet::ClassHash; -use starknet::ContractAddress; -use starknet::SyscallResultTrait; use starknet::class_hash::class_hash_const; -use starknet::contract_address_const; use starknet::secp256_trait::Secp256Trait; +use starknet::{ClassHash, ContractAddress, SyscallResultTrait, contract_address_const}; pub type EthPublicKey = starknet::secp256k1::Secp256k1Point; -pub const DECIMALS: u8 = 18_u8; -pub const SUPPLY: u256 = 2000; +pub const DECIMALS: u8 = 18; +pub const SUPPLY: u256 = 2_000; pub const VALUE: u256 = 300; pub const FELT_VALUE: felt252 = 'FELT_VALUE'; pub const ROLE: felt252 = 'ROLE'; @@ -104,13 +101,12 @@ pub fn OPERATOR() -> ContractAddress { } pub fn DATA(success: bool) -> Span { - let mut data = array![]; - if success { - data.append(SUCCESS); + let value = if success { + SUCCESS } else { - data.append(FAILURE); - } - data.span() + FAILURE + }; + array![value].span() } pub fn EMPTY_DATA() -> Span { diff --git a/packages/testing/src/deployment.cairo b/packages/testing/src/deployment.cairo index c58bfb061..85503f385 100644 --- a/packages/testing/src/deployment.cairo +++ b/packages/testing/src/deployment.cairo @@ -4,6 +4,15 @@ use snforge_std::{declare, get_class_hash, ContractClass, ContractClassTrait}; use snforge_std::{start_cheat_caller_address, stop_cheat_caller_address}; use starknet::ContractAddress; +/// Declares a contract with a `snforge` `declare` call and unwraps the result. +pub fn declare_class(contract_name: ByteArray) -> ContractClass { + match snforge_std::declare(contract_name) { + Result::Ok(contract_class) => contract_class, + Result::Err(panic_data) => panic!("{}", panic_data_to_byte_array(panic_data)) + } +} + +/// Deploys an instance of a contract and unwraps the result. pub fn deploy(contract_class: ContractClass, calldata: Array) -> ContractAddress { match contract_class.deploy(@calldata) { Result::Ok((contract_address, _)) => contract_address, @@ -11,16 +20,17 @@ pub fn deploy(contract_class: ContractClass, calldata: Array) -> Contra } } +/// Deploys a contract at the given address and unwraps the result. pub fn deploy_at( - contract_class: ContractClass, contract_address: ContractAddress, calldata: Array + contract_class: ContractClass, target_address: ContractAddress, calldata: Array ) { - match contract_class.deploy_at(@calldata, contract_address) { + match contract_class.deploy_at(@calldata, target_address) { Result::Ok(_) => (), Result::Err(panic_data) => panic!("{}", panic_data_to_byte_array(panic_data)) }; } -/// Deploys a contract from the class hash of another contract which is already deployed. +/// Deploys a contract using the class hash from another already-deployed contract. pub fn deploy_another_at( existing: ContractAddress, target_address: ContractAddress, calldata: Array ) { @@ -29,18 +39,14 @@ pub fn deploy_another_at( deploy_at(contract_class, target_address, calldata) } -pub fn declare_class(contract_name: ByteArray) -> ContractClass { - match snforge_std::declare(contract_name) { - Result::Ok(contract_class) => contract_class, - Result::Err(panic_data) => panic!("{}", panic_data_to_byte_array(panic_data)) - } -} - +/// Combines the declaration of a class and the deployment of a contract into one function call. pub fn declare_and_deploy(contract_name: ByteArray, calldata: Array) -> ContractAddress { let contract_class = declare_class(contract_name); deploy(contract_class, calldata) } +/// Combines the declaration of a class and the deployment of a contract at the given address +/// into one function call. pub fn declare_and_deploy_at( contract_name: ByteArray, target_address: ContractAddress, calldata: Array ) { diff --git a/packages/testing/src/events.cairo b/packages/testing/src/events.cairo index 302d1a917..d77d72904 100644 --- a/packages/testing/src/events.cairo +++ b/packages/testing/src/events.cairo @@ -3,13 +3,15 @@ use starknet::ContractAddress; #[generate_trait] pub impl EventSpyExtImpl of EventSpyExt { + /// Ensures that `from_address` has emitted only the `expected_event` and no additional events. fn assert_only_event, +Drop>( - ref self: EventSpy, from_address: ContractAddress, event: T + ref self: EventSpy, from_address: ContractAddress, expected_event: T ) { - self.assert_emitted_single(from_address, event); + self.assert_emitted_single(from_address, expected_event); self.assert_no_events_left_from(from_address); } + /// Ensures that `from_address` has emitted the `expected_event`. fn assert_emitted_single, +Drop>( ref self: EventSpy, from_address: ContractAddress, expected_event: T ) { @@ -17,10 +19,13 @@ pub impl EventSpyExtImpl of EventSpyExt { self._event_offset += 1; } + /// Removes a single event from the queue. If the queue is empty, the function will panic. fn drop_event(ref self: EventSpy) { self.drop_n_events(1); } + /// Removes `number_to_drop` events from the queue. If the queue is empty, the function will + /// panic. fn drop_n_events(ref self: EventSpy, number_to_drop: u32) { let events = self.get_events().events; let len = events.len(); @@ -31,20 +36,25 @@ pub impl EventSpyExtImpl of EventSpyExt { self._event_offset += number_to_drop; } + /// Removes all events remaining on the queue. If the queue is empty already, the function will + /// do nothing. fn drop_all_events(ref self: EventSpy) { let events = self.get_events().events; self._event_offset += events.len(); } + /// Ensures that there are no events remaining on the queue. fn assert_no_events_left(ref self: EventSpy) { let events = self.get_events().events; assert!(events.len() == 0, "Events remaining on queue"); } + /// Ensures that there are no events emitted from the given address remaining on the queue. fn assert_no_events_left_from(ref self: EventSpy, from_address: ContractAddress) { assert!(self.count_events_from(from_address) == 0, "Events remaining on queue"); } + /// Counts the number of remaining events emitted from the given address. fn count_events_from(ref self: EventSpy, from_address: ContractAddress) -> u32 { let mut result = 0; let mut events = self.get_events().events; diff --git a/packages/testing/src/signing.cairo b/packages/testing/src/signing.cairo index 8b1e7ca44..2a45f1a0f 100644 --- a/packages/testing/src/signing.cairo +++ b/packages/testing/src/signing.cairo @@ -6,24 +6,29 @@ use starknet::secp256k1::Secp256k1Point; pub type StarkKeyPair = KeyPair; pub type Secp256k1KeyPair = KeyPair; +/// Builds a Stark Key Pair from a private key represented by a `felt252` value. pub fn get_stark_keys_from(private_key: felt252) -> StarkKeyPair { StarkCurveKeyPairImpl::from_secret_key(private_key) } +/// Builds a Secp256k1 Key Pair from a private key represented by a `u256` value. pub fn get_secp256k1_keys_from(private_key: u256) -> Secp256k1KeyPair { Secp256k1CurveKeyPairImpl::from_secret_key(private_key) } -#[generate_trait] -pub impl StarkKeyPairExt of StarkKeyPairExtTrait { +/// A helper trait that facilitates converting a signature into a serialized format. +pub trait SerializedSigning { + fn serialized_sign(self: KP, msg: M) -> Array; +} + +pub impl StarkSerializedSigning of SerializedSigning { fn serialized_sign(self: StarkKeyPair, msg: felt252) -> Array { let (r, s) = self.sign(msg).unwrap(); array![r, s] } } -#[generate_trait] -pub impl Secp256k1KeyPairExt of Secp256k1KeyPairExtTrait { +pub impl Secp256k1SerializedSigning of SerializedSigning { fn serialized_sign(self: Secp256k1KeyPair, msg: u256) -> Array { let (r, s) = self.sign(msg).unwrap(); array![r.low.into(), r.high.into(), s.low.into(), s.high.into()] diff --git a/packages/utils/src/deployments.cairo b/packages/utils/src/deployments.cairo index bec5bed0a..8524cdea6 100644 --- a/packages/utils/src/deployments.cairo +++ b/packages/utils/src/deployments.cairo @@ -9,13 +9,12 @@ use core::pedersen::PedersenTrait; use core::poseidon::PoseidonTrait; use interface::IUniversalDeployer; use openzeppelin_utils::serde::SerializedAppend; -use starknet::ClassHash; -use starknet::ContractAddress; +use starknet::{ClassHash, ContractAddress}; // 2**251 - 256 -const L2_ADDRESS_UPPER_BOUND: felt252 = +pub const L2_ADDRESS_UPPER_BOUND: felt252 = 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00; -const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; +pub const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; /// Returns the contract address from a `deploy_syscall`. /// `deployer_address` should be the zero address if the deployment is origin-independent (deployed @@ -86,14 +85,12 @@ pub fn calculate_contract_address_from_udc( .update_with(deployer_info.caller_address) .update_with(salt) .finalize(); - return calculate_contract_address_from_deploy_syscall( + calculate_contract_address_from_deploy_syscall( hashed_salt, class_hash, constructor_calldata, deployer_info.udc_address - ); - }, - Option::None => { - return calculate_contract_address_from_deploy_syscall( - salt, class_hash, constructor_calldata, Zero::zero() - ); + ) }, + Option::None => calculate_contract_address_from_deploy_syscall( + salt, class_hash, constructor_calldata, Zero::zero() + ), } } diff --git a/packages/utils/src/deployments/interface.cairo b/packages/utils/src/deployments/interface.cairo index 10231a5a9..d747ae145 100644 --- a/packages/utils/src/deployments/interface.cairo +++ b/packages/utils/src/deployments/interface.cairo @@ -1,8 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/universal_deployer/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/deployments/interface.cairo) -use starknet::ClassHash; -use starknet::ContractAddress; +use starknet::{ClassHash, ContractAddress}; #[starknet::interface] pub trait IUniversalDeployer { diff --git a/packages/utils/src/lib.cairo b/packages/utils/src/lib.cairo index 40d3b3539..d35458d10 100644 --- a/packages/utils/src/lib.cairo +++ b/packages/utils/src/lib.cairo @@ -14,15 +14,12 @@ mod tests; pub mod unwrap_and_cast; -pub use cryptography::nonces; -pub use cryptography::snip12; +pub use cryptography::{nonces, snip12}; pub use unwrap_and_cast::UnwrapAndCast; - -use starknet::ContractAddress; -use starknet::SyscallResult; -use starknet::SyscallResultTrait; use starknet::syscalls::call_contract_syscall; +use starknet::{ContractAddress, SyscallResult, SyscallResultTrait}; + pub fn try_selector_with_fallback( target: ContractAddress, selector: felt252, fallback: felt252, args: Span ) -> SyscallResult> {