diff --git a/.github/workflows/on-pull-request.yaml b/.github/workflows/on-pull-request.yaml index 0ffe4a5..23d6d47 100644 --- a/.github/workflows/on-pull-request.yaml +++ b/.github/workflows/on-pull-request.yaml @@ -29,3 +29,6 @@ jobs: - name: run test and coverage run: scarb test -w --coverage + + - name: verify primer class hash + run: ./scripts/verify_primer_class_hash.sh diff --git a/Scarb.lock b/Scarb.lock index 7f52054..3f440d4 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,15 +1,43 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "account_factory" +version = "0.1.0" +dependencies = [ + "contracts", + "openzeppelin", + "snforge_std", + "starkware_utils", + "starkware_utils_testing", + "testing_utils", +] + [[package]] name = "contracts" version = "0.1.0" +dependencies = [ + "snforge_std", + "starkware_utils_testing", +] + +[[package]] +name = "earn_reporter" +version = "0.1.0" dependencies = [ "openzeppelin", "openzeppelin_upgrades", "snforge_std", - "starkware_utils", "starkware_utils_testing", + "testing_utils", +] + +[[package]] +name = "eth_712_account" +version = "0.1.0" +dependencies = [ + "openzeppelin", + "snforge_std", ] [[package]] @@ -162,3 +190,29 @@ dependencies = [ "snforge_std", "starkware_utils", ] + +[[package]] +name = "strategy_implementation" +version = "0.1.0" +dependencies = [ + "account_factory", + "contracts", + "earn_reporter", + "openzeppelin", + "snforge_std", + "starkware_utils", + "starkware_utils_testing", + "testing_utils", +] + +[[package]] +name = "testing_utils" +version = "0.1.0" +dependencies = [ + "account_factory", + "contracts", + "openzeppelin", + "snforge_std", + "starkware_utils", + "starkware_utils_testing", +] diff --git a/Scarb.toml b/Scarb.toml index bef16ec..f366b93 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,5 +1,5 @@ [workspace] -members = ["contracts"] +members = ["contracts", "eth_712_account", "account_factory", "earn_reporter", "strategy_implementation", "testing_utils"] [workspace.package] edition = "2024_07" @@ -9,7 +9,6 @@ version = "0.1.0" assert_macros = "2.12.3" openzeppelin = "2.0.0" openzeppelin_upgrades = "2.0.0" -openzeppelin_testing = "3.0.0" snforge_std = "0.54.1" starknet = "2.12.2" starkware_utils = {git = "https://github.com/starkware-libs/starkware-starknet-utils", rev = "9aa2ec0b1dc1bc54bc01705cb2d4f5227e28e1e7"} diff --git a/account_factory/Scarb.toml b/account_factory/Scarb.toml new file mode 100644 index 0000000..4b09068 --- /dev/null +++ b/account_factory/Scarb.toml @@ -0,0 +1,37 @@ +[package] +name = "account_factory" +edition.workspace = true +version.workspace = true + +[lib] + +[dependencies] +contracts = { path = "../contracts" } +openzeppelin.workspace = true +starknet.workspace = true +starkware_utils.workspace = true + +[dev-dependencies] +assert_macros.workspace = true +snforge_std.workspace = true +starkware_utils_testing.workspace = true +testing_utils = { path = "../testing_utils" } + +[[target.starknet-contract]] +sierra = true + +[[test]] +build-external-contracts = [ + "contracts::primer::primer::Primer", + "testing_utils::dummy_contracts::DummyEthAddressContract", + "testing_utils::dummy_contracts::SecondDummyEthAddressContract", +] +name = "account_factory_unittest" + +[profile.dev.cairo] +unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" + +[scripts] +test = "snforge test" diff --git a/contracts/src/account_factory/account_factory.cairo b/account_factory/src/account_factory.cairo similarity index 98% rename from contracts/src/account_factory/account_factory.cairo rename to account_factory/src/account_factory.cairo index c487162..f273f71 100644 --- a/contracts/src/account_factory/account_factory.cairo +++ b/account_factory/src/account_factory.cairo @@ -13,8 +13,8 @@ pub trait IAccountFactory { #[starknet::contract] pub mod AccountFactory { use RolesComponent::InternalTrait as RolesInternalTrait; - use contracts::account_factory::account_factory::IAccountFactory; - use contracts::account_factory::utils::{ + use account_factory::account_factory::IAccountFactory; + use account_factory::utils::{ IEthAccountInitializerDispatcher, IEthAccountInitializerDispatcherTrait, PRIMER_CLASS_HASH, eth_address_to_account, is_deployed, }; diff --git a/contracts/src/account_factory.cairo b/account_factory/src/lib.cairo similarity index 71% rename from contracts/src/account_factory.cairo rename to account_factory/src/lib.cairo index a7f1bcf..6835736 100644 --- a/contracts/src/account_factory.cairo +++ b/account_factory/src/lib.cairo @@ -1,4 +1,4 @@ pub mod account_factory; #[cfg(test)] -pub mod test; +pub(crate) mod test; pub mod utils; diff --git a/contracts/src/account_factory/test.cairo b/account_factory/src/test.cairo similarity index 96% rename from contracts/src/account_factory/test.cairo rename to account_factory/src/test.cairo index a69a6be..95a2f12 100644 --- a/contracts/src/account_factory/test.cairo +++ b/account_factory/src/test.cairo @@ -1,20 +1,17 @@ +use account_factory::account_factory::AccountFactory::{AccountClassHashChanged, AccountDeployed}; +use account_factory::account_factory::{IAccountFactoryDispatcher, IAccountFactoryDispatcherTrait}; use snforge_std; use snforge_std::cheatcodes::events::{EventSpyTrait, EventsFilterTrait}; use starknet::secp256_trait::Signature; use starknet::syscalls::get_class_hash_at_syscall; use starknet::{ClassHash, ContractAddress, EthAddress, SyscallResultTrait}; use starkware_utils_testing::test_utils::{assert_expected_event_emitted, cheat_caller_address_once}; -use crate::account_factory::account_factory::AccountFactory::{ - AccountClassHashChanged, AccountDeployed, -}; -use crate::account_factory::account_factory::{ - IAccountFactoryDispatcher, IAccountFactoryDispatcherTrait, -}; -use crate::test_utils::{ - APP_GOVERNOR, declare_dummy_eth_address_contract, declare_second_dummy_eth_address_contract, - eth_address_to_account, get_event_by_selector, get_event_by_selector_n, - setup_account_factory_test_env, +use testing_utils::account_factory_utils::{eth_address_to_account, setup_account_factory_test_env}; +use testing_utils::constants::APP_GOVERNOR; +use testing_utils::dummy_contracts::{ + declare_dummy_eth_address_contract, declare_second_dummy_eth_address_contract, }; +use testing_utils::event_helpers::{get_event_by_selector, get_event_by_selector_n}; fn deploy_account_wrapper( @@ -286,4 +283,3 @@ fn test_change_account_class_hash_affects_only_new_users() { "original account should keep its original class hash", ); } - diff --git a/contracts/src/account_factory/utils.cairo b/account_factory/src/utils.cairo similarity index 87% rename from contracts/src/account_factory/utils.cairo rename to account_factory/src/utils.cairo index f8d739f..9ffd9e1 100644 --- a/contracts/src/account_factory/utils.cairo +++ b/account_factory/src/utils.cairo @@ -7,14 +7,14 @@ use starknet::{ClassHash, ContractAddress, EthAddress, SyscallResultTrait, get_c pub(crate) const CONTRACT_ADDRESS_SALT: felt252 = 0; #[cfg(target: "test")] -pub(crate) const PRIMER_CLASS_HASH: ClassHash = - 0x279a9bb18604f4ae57633373d56656063203f236cc5aeceea8f2cf40f6336d7 +pub const PRIMER_CLASS_HASH: ClassHash = + 0x0279a9bb18604f4ae57633373d56656063203f236cc5aeceea8f2cf40f6336d7 .try_into() .unwrap(); #[cfg(not(target: "test"))] -pub(crate) const PRIMER_CLASS_HASH: ClassHash = - 0x123e6bc1c14ae9934e933d3f64916a6116dd6b036a922b2b1f0815e0d1d300 +pub const PRIMER_CLASS_HASH: ClassHash = + 0x00123e6bc1c14ae9934e933d3f64916a6116dd6b036a922b2b1f0815e0d1d300 .try_into() .unwrap(); @@ -26,6 +26,7 @@ pub(crate) trait IEthAccountInitializer { } /// Computes the Pedersen hash on the elements of the span using a hash state. +// TODO: Move this function to a shared utils package to avoid duplication across packages. pub fn compute_pedersen_on_elements(data: Span) -> felt252 { let mut state = PedersenTrait::new(0); for value in data { @@ -72,4 +73,3 @@ pub fn compute_contract_address( pub fn is_deployed(addr: ContractAddress) -> bool { get_class_hash_at_syscall(addr).unwrap_syscall() != Zero::zero() } - diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 246ff78..44ddcf3 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -1,36 +1,30 @@ [package] -edition.workspace = true name = "contracts" -version.workspace = true +# Pinned to ensure Primer class hash stability - do not change, is it would break PRIMER_CLASS_HASH +edition = "2024_07" +version = "0.1.0" + +[lib] [dependencies] -openzeppelin.workspace = true -openzeppelin_upgrades.workspace = true -starknet.workspace = true -starkware_utils.workspace = true +# Pinned to ensure Primer class hash stability - do not change, is it would break PRIMER_CLASS_HASH +starknet = "2.12.2" [dev-dependencies] -assert_macros.workspace = true snforge_std.workspace = true starkware_utils_testing.workspace = true [[target.starknet-contract]] sierra = true -[scripts] -test = "snforge test" - -[lib] - [profile.dev.cairo] -inlining-strategy = "avoid" -panic-backtrace = true -unstable-add-statements-code-locations-debug-info = true unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" [profile.release.cairo] -inlining-strategy = 30 +# Explicit release settings for deterministic Primer class hash +sierra-replace-ids = true -[[test]] -build-external-contracts = ["starkware_utils::erc20::erc20_mocks::DualCaseERC20Mock"] -name = "contracts_unittest" +[scripts] +test = "snforge test" diff --git a/contracts/src/earn_reporter.cairo b/contracts/src/earn_reporter.cairo deleted file mode 100644 index 8160b07..0000000 --- a/contracts/src/earn_reporter.cairo +++ /dev/null @@ -1,3 +0,0 @@ -pub mod earn_reporter; -#[cfg(test)] -pub mod test; diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index ec12fb5..6fe721e 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,8 +1,4 @@ -pub mod account_factory; -pub mod earn_reporter; -pub mod eth_712_account; -pub(crate) mod known_addresses; pub mod primer; -pub mod strategy_implementation; + #[cfg(test)] -pub(crate) mod test_utils; +mod test; diff --git a/contracts/src/primer.cairo b/contracts/src/primer.cairo index e04386f..cca4fbf 100644 --- a/contracts/src/primer.cairo +++ b/contracts/src/primer.cairo @@ -1,3 +1 @@ pub mod primer; -#[cfg(test)] -pub mod test; diff --git a/contracts/src/primer/test.cairo b/contracts/src/test.cairo similarity index 58% rename from contracts/src/primer/test.cairo rename to contracts/src/test.cairo index 04a872a..0547564 100644 --- a/contracts/src/primer/test.cairo +++ b/contracts/src/test.cairo @@ -1,11 +1,23 @@ -use snforge_std::{ContractClassTrait, DeclareResultTrait, TokenImpl}; -use starknet::get_contract_address; +use contracts::primer::primer::{IPrimerDispatcher, IPrimerDispatcherTrait}; +use snforge_std::{ContractClassTrait, DeclareResultTrait}; use starknet::syscalls::get_class_hash_at_syscall; -use crate::primer::primer::{IPrimerDispatcher, IPrimerDispatcherTrait}; -use crate::test_utils::declare_dummy_eth_address_contract; +use starknet::{ClassHash, SyscallResultTrait, get_contract_address}; +use starkware_utils_testing::test_utils::cheat_caller_address_once; + +// Minimal no-op contract used as upgrade target in tests. +#[starknet::contract] +mod DummyUpgradeTarget { + #[storage] + struct Storage {} +} + +fn declare_dummy_upgrade_target() -> ClassHash { + *snforge_std::declare("DummyUpgradeTarget").unwrap_syscall().contract_class().class_hash +} + #[test] #[should_panic(expected: 'INVALID_CALLER')] -fn test_primer_update_class_hash_invalid_caller() { +fn test_primer_set_class_hash_invalid_caller() { /// set_class_hash should only be callable by the upgrade account set at construction. /// Here we impersonate a different caller and expect the function to panic with /// 'INVALID_CALLER'. @@ -14,16 +26,16 @@ fn test_primer_update_class_hash_invalid_caller() { let primer = IPrimerDispatcher { contract_address: primer_addr }; // Impersonate a non-upgrade caller for the next call. - starkware_utils_testing::test_utils::cheat_caller_address_once( + cheat_caller_address_once( contract_address: primer_addr, caller_address: 0x1.try_into().unwrap(), ); // Attempt to update class hash with the wrong caller - should panic (see attribute above). - let dummy_eth_address_contract_class_hash = declare_dummy_eth_address_contract(); - primer.set_class_hash(new_class_hash: dummy_eth_address_contract_class_hash); + let test_class_hash = declare_dummy_upgrade_target(); + primer.set_class_hash(new_class_hash: test_class_hash); } #[test] -fn test_primer_sanity_set_class_hash() { +fn test_primer_set_class_hash_success() { /// Happy path: after deployment, impersonate the upgrade account and update class hash. /// Verifies the on-chain class hash equals the provided value. let primer_class = snforge_std::declare("Primer").unwrap().contract_class(); @@ -32,12 +44,11 @@ fn test_primer_sanity_set_class_hash() { let primer = IPrimerDispatcher { contract_address: primer_addr }; // Impersonate the upgrade account (same address used by the test infra for this call). // Update the class hash and assert it took effect. - let dummy_eth_address_contract_class_hash = declare_dummy_eth_address_contract(); - starkware_utils_testing::test_utils::cheat_caller_address_once( + let test_class_hash = declare_dummy_upgrade_target(); + cheat_caller_address_once( contract_address: primer_addr, caller_address: get_contract_address(), ); - primer.set_class_hash(new_class_hash: dummy_eth_address_contract_class_hash); + primer.set_class_hash(new_class_hash: test_class_hash); let class_hash = get_class_hash_at_syscall(primer_addr).unwrap(); - assert!(class_hash == dummy_eth_address_contract_class_hash, "class hash mismatch"); + assert!(class_hash == test_class_hash, "class hash mismatch"); } - diff --git a/contracts/src/test_utils.cairo b/contracts/src/test_utils.cairo deleted file mode 100644 index e5bb2ce..0000000 --- a/contracts/src/test_utils.cairo +++ /dev/null @@ -1,203 +0,0 @@ -use contracts::account_factory::utils::{PRIMER_CLASS_HASH, compute_contract_address}; -use snforge_std::cheatcodes::events::Event; -use snforge_std::{ContractClassTrait, DeclareResultTrait, TokenImpl}; -use starknet::eth_address::EthAddress; -use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; -use starkware_utils::constants::SYMBOL; -use starkware_utils_testing::test_utils::{ - set_account_as_app_governor, set_account_as_app_role_admin, -}; - -pub(crate) fn APP_ROLE_ADMIN() -> ContractAddress { - 'APP_ROLE_ADMIN'.try_into().unwrap() -} - -pub(crate) fn APP_GOVERNOR() -> ContractAddress { - 'APP_GOVERNOR'.try_into().unwrap() -} - - -pub(crate) fn GOVERNANCE_ADMIN() -> ContractAddress { - 'GOVERNANCE_ADMIN'.try_into().unwrap() -} - -/// Returns the index of the nth event whose first key equals the given selector. -pub(crate) fn find_event_index_by_selector( - events: Span<(ContractAddress, Event)>, selector: felt252, n: usize, -) -> Option { - let mut i = 0_usize; - let mut seen = 0_usize; - for (_, ev) in events { - if ev.keys.len() > 0 && *ev.keys.at(0) == selector { - if seen == n { - return Option::Some(i); - } - seen += 1; - } - i += 1; - } - None -} -/// Returns a cloned copy of the first event emitted with the given selector (if any). -pub(crate) fn get_event_by_selector( - events: Span<(ContractAddress, Event)>, selector: felt252, -) -> Option<@(ContractAddress, Event)> { - match find_event_index_by_selector(:events, :selector, n: 0) { - Option::Some(i) => { - let (from, ev) = events.at(i); - Option::Some(@(*from, ev.clone())) - }, - None => None, - } -} - -/// Returns a cloned copy of the nth event emitted with the given selector (if any). -pub(crate) fn get_event_by_selector_n( - events: Span<(ContractAddress, Event)>, selector: felt252, n: usize, -) -> Option<@(ContractAddress, Event)> { - match find_event_index_by_selector(:events, :selector, :n) { - Option::Some(i) => { - let (from, ev) = events.at(i); - Option::Some(@(*from, ev.clone())) - }, - None => None, - } -} - - -/// Mirrors AccountFactory.eth_address_to_account for tests, using the -/// PRIMER_CLASS_HASH and the account factory address as the deployer address. The difference -/// between this and AccountFactory.eth_address_to_account is that this function gets the deployer -/// address from the caller. -pub(crate) fn eth_address_to_account( - account_factory: ContractAddress, eth_address: EthAddress, -) -> ContractAddress { - compute_contract_address( - salt: eth_address.into(), - class_hash: PRIMER_CLASS_HASH.into(), - constructor_calldata: array![].span(), - deployer_address: account_factory.into(), - ) -} - -//TODO - Move to starkware_utils_testing -pub(crate) fn deploy_mock_erc20_contract( - initial_supply: u256, owner_address: ContractAddress, name: ByteArray, -) -> ContractAddress { - let mut calldata = ArrayTrait::new(); - name.serialize(ref calldata); - SYMBOL().serialize(ref calldata); - initial_supply.serialize(ref calldata); - owner_address.serialize(ref calldata); - let erc20_contract = snforge_std::declare("DualCaseERC20Mock") - .unwrap_syscall() - .contract_class(); - let (token_address, _) = erc20_contract.deploy(@calldata).unwrap_syscall(); - token_address -} - -/// Declare the `Primer` contract and return its class hash. -pub(crate) fn declare_primer_contract() -> ClassHash { - *snforge_std::declare("Primer").unwrap_syscall().contract_class().class_hash -} - - -fn set_account_factory_default_roles(account_factory: ContractAddress) { - // App role admin - set_account_as_app_role_admin( - contract: account_factory, account: APP_ROLE_ADMIN(), governance_admin: GOVERNANCE_ADMIN(), - ); - // App governor (requires app role admin) - set_account_as_app_governor( - contract: account_factory, account: APP_GOVERNOR(), app_role_admin: APP_ROLE_ADMIN(), - ); -} - - -/// Sets up the AccountFactory test environment: -/// - deploys the `AccountFactory` contract, -/// - sets default roles, -/// - declares the `Primer` contract so its class hash is available. -pub(crate) fn setup_account_factory_test_env() -> ContractAddress { - let calldata = account_factory_constructor_calldata(); - let account_factory_contract = snforge_std::declare("AccountFactory") - .unwrap_syscall() - .contract_class(); - let (account_factory_contract_address, _) = account_factory_contract - .deploy(@calldata) - .unwrap_syscall(); - set_account_factory_default_roles(account_factory_contract_address); - declare_primer_contract(); - account_factory_contract_address -} - -/// Builds the constructor calldata array for AccountFactory. -pub(crate) fn account_factory_constructor_calldata() -> Array { - let governance_admin: ContractAddress = GOVERNANCE_ADMIN(); - let upgrade_delay: u64 = 0; - let account_class_hash: ClassHash = declare_dummy_eth_address_contract(); - let mut calldata: Array = array![]; - Serde::serialize(@governance_admin, ref calldata); - Serde::serialize(@upgrade_delay, ref calldata); - Serde::serialize(@account_class_hash, ref calldata); - calldata -} - -// Minimal no-op contract for tests: stores an EthAddress passed at construction. -#[starknet::contract] -pub mod DummyEthAddressContract { - use starknet::eth_address::EthAddress; - use starknet::secp256_trait::Signature; - #[storage] - struct Storage {} - - - #[external(v0)] - fn initialize(ref self: ContractState, eth_address: EthAddress, signature: Signature) { - return; - } -} - - -pub(crate) fn declare_dummy_eth_address_contract() -> ClassHash { - *snforge_std::declare("DummyEthAddressContract").unwrap_syscall().contract_class().class_hash -} - - -/// Declare the `SecondDummyEthAddressContract` contract and return its class hash. -#[starknet::contract] -pub mod SecondDummyEthAddressContract { - use starknet::eth_address::EthAddress; - use starknet::secp256_trait::Signature; - #[storage] - struct Storage {} - - #[constructor] - pub fn constructor(ref self: ContractState) { - // This assert is just to get a different class hash for the contract. - assert!(true, "ERROR"); - } - #[external(v0)] - fn initialize(ref self: ContractState, eth_address: EthAddress, signature: Signature) { - return; - } -} - -/// Declare the `SecondDummyEthAddressContract` contract and return its class hash. -pub(crate) fn declare_second_dummy_eth_address_contract() -> ClassHash { - *snforge_std::declare("SecondDummyEthAddressContract") - .unwrap_syscall() - .contract_class() - .class_hash -} - -/// Deploy the EarnReporter contract and return its address. -pub(crate) fn deploy_earn_reporter(owner: ContractAddress) -> ContractAddress { - let earn_reporter_class = snforge_std::declare("EarnReporter") - .unwrap_syscall() - .contract_class(); - let (earn_reporter_addr, _) = earn_reporter_class - .deploy(@array![owner.into()]) - .unwrap_syscall(); - earn_reporter_addr -} diff --git a/earn_reporter/Scarb.toml b/earn_reporter/Scarb.toml new file mode 100644 index 0000000..0f24f14 --- /dev/null +++ b/earn_reporter/Scarb.toml @@ -0,0 +1,33 @@ +[package] +name = "earn_reporter" +edition.workspace = true +version.workspace = true + +[lib] + +[dependencies] +openzeppelin.workspace = true +openzeppelin_upgrades.workspace = true +starknet.workspace = true + +[dev-dependencies] +snforge_std.workspace = true +starkware_utils_testing.workspace = true +testing_utils = { path = "../testing_utils" } + +[[target.starknet-contract]] +sierra = true + +[[test]] +build-external-contracts = [ + "testing_utils::dummy_contracts::DummyEthAddressContract", +] +name = "earn_reporter_unittest" + +[profile.dev.cairo] +unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" + +[scripts] +test = "snforge test" diff --git a/contracts/src/earn_reporter/earn_reporter.cairo b/earn_reporter/src/earn_reporter.cairo similarity index 98% rename from contracts/src/earn_reporter/earn_reporter.cairo rename to earn_reporter/src/earn_reporter.cairo index d0ed209..fd523fa 100644 --- a/contracts/src/earn_reporter/earn_reporter.cairo +++ b/earn_reporter/src/earn_reporter.cairo @@ -22,7 +22,7 @@ pub trait IEarnReporter { #[starknet::contract] pub mod EarnReporter { - use contracts::earn_reporter::earn_reporter::IEarnReporter; + use earn_reporter::earn_reporter::IEarnReporter; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::access::ownable::OwnableComponent::InternalTrait as OwnableInternalTrait; use openzeppelin_upgrades::UpgradeableComponent; diff --git a/earn_reporter/src/lib.cairo b/earn_reporter/src/lib.cairo new file mode 100644 index 0000000..35d2e09 --- /dev/null +++ b/earn_reporter/src/lib.cairo @@ -0,0 +1,5 @@ +pub mod earn_reporter; +#[cfg(test)] +pub(crate) mod test; +#[cfg(test)] +pub(crate) mod test_utils; diff --git a/contracts/src/earn_reporter/test.cairo b/earn_reporter/src/test.cairo similarity index 93% rename from contracts/src/earn_reporter/test.cairo rename to earn_reporter/src/test.cairo index ea68ba9..211a139 100644 --- a/contracts/src/earn_reporter/test.cairo +++ b/earn_reporter/src/test.cairo @@ -1,15 +1,13 @@ -use contracts::earn_reporter::earn_reporter::EarnReporter::OrderCreated; -use contracts::earn_reporter::earn_reporter::{ - IEarnReporterDispatcher, IEarnReporterDispatcherTrait, -}; +use earn_reporter::earn_reporter::EarnReporter::OrderCreated; +use earn_reporter::earn_reporter::{IEarnReporterDispatcher, IEarnReporterDispatcherTrait}; +use earn_reporter::test_utils::deploy_earn_reporter; use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; use snforge_std::cheatcodes::events::{EventSpyTrait, EventsFilterTrait}; use starknet::syscalls::get_class_hash_at_syscall; use starknet::{ContractAddress, SyscallResultTrait, get_contract_address}; use starkware_utils_testing::test_utils::{assert_expected_event_emitted, cheat_caller_address_once}; -use crate::test_utils::{ - declare_dummy_eth_address_contract, deploy_earn_reporter, get_event_by_selector, -}; +use testing_utils::dummy_contracts::declare_dummy_eth_address_contract; +use testing_utils::event_helpers::get_event_by_selector; fn default_order_created_event() -> OrderCreated { OrderCreated { diff --git a/earn_reporter/src/test_utils.cairo b/earn_reporter/src/test_utils.cairo new file mode 100644 index 0000000..87ccde5 --- /dev/null +++ b/earn_reporter/src/test_utils.cairo @@ -0,0 +1,13 @@ +use snforge_std::{ContractClassTrait, DeclareResultTrait}; +use starknet::{ContractAddress, SyscallResultTrait}; + +/// Deploy the EarnReporter contract and return its address. +pub(crate) fn deploy_earn_reporter(owner: ContractAddress) -> ContractAddress { + let earn_reporter_class = snforge_std::declare("EarnReporter") + .unwrap_syscall() + .contract_class(); + let (earn_reporter_addr, _) = earn_reporter_class + .deploy(@array![owner.into()]) + .unwrap_syscall(); + earn_reporter_addr +} diff --git a/eth_712_account/Scarb.toml b/eth_712_account/Scarb.toml new file mode 100644 index 0000000..f08f6b4 --- /dev/null +++ b/eth_712_account/Scarb.toml @@ -0,0 +1,23 @@ +[package] +name = "eth_712_account" +edition.workspace = true +version.workspace = true + +[dependencies] +openzeppelin.workspace = true +starknet.workspace = true + +[dev-dependencies] +assert_macros.workspace = true +snforge_std.workspace = true + +[[target.starknet-contract]] +sierra = true + +[profile.dev.cairo] +unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" + +[scripts] +test = "snforge test" diff --git a/contracts/src/eth_712_account/eth_712_account.cairo b/eth_712_account/src/eth_712_account.cairo similarity index 98% rename from contracts/src/eth_712_account/eth_712_account.cairo rename to eth_712_account/src/eth_712_account.cairo index 8429d20..70f7579 100644 --- a/contracts/src/eth_712_account/eth_712_account.cairo +++ b/eth_712_account/src/eth_712_account.cairo @@ -12,13 +12,13 @@ #[starknet::contract(account)] pub mod StarknetEth712Account { - use contracts::eth_712_account::eth_712_utils::{ + use core::num::traits::Zero; + use eth_712_account::eth_712_utils::{ assert_valid_owner, extract_signature, get_outside_execution_hash, is_valid_signature, }; - use contracts::eth_712_account::interface::{ + use eth_712_account::interface::{ IAccount712Admin, IEICDispatcherTrait, IEICLibraryDispatcher, Upgraded, }; - use core::num::traits::Zero; use openzeppelin::account::AccountComponent; use openzeppelin::account::extensions::src9::interface::ISRC9_V2_ID; use openzeppelin::account::extensions::src9::{ISRC9_V2, OutsideExecution}; diff --git a/contracts/src/eth_712_account/eth_712_utils.cairo b/eth_712_account/src/eth_712_utils.cairo similarity index 100% rename from contracts/src/eth_712_account/eth_712_utils.cairo rename to eth_712_account/src/eth_712_utils.cairo diff --git a/contracts/src/eth_712_account/interface.cairo b/eth_712_account/src/interface.cairo similarity index 100% rename from contracts/src/eth_712_account/interface.cairo rename to eth_712_account/src/interface.cairo diff --git a/contracts/src/eth_712_account.cairo b/eth_712_account/src/lib.cairo similarity index 100% rename from contracts/src/eth_712_account.cairo rename to eth_712_account/src/lib.cairo diff --git a/contracts/src/eth_712_account/register_interfaces_eic.cairo b/eth_712_account/src/register_interfaces_eic.cairo similarity index 95% rename from contracts/src/eth_712_account/register_interfaces_eic.cairo rename to eth_712_account/src/register_interfaces_eic.cairo index 840e632..ed24149 100644 --- a/contracts/src/eth_712_account/register_interfaces_eic.cairo +++ b/eth_712_account/src/register_interfaces_eic.cairo @@ -5,7 +5,7 @@ #[starknet::contract] pub mod RegisterInterfacesEIC { - use contracts::eth_712_account::interface::IEIC; + use eth_712_account::interface::IEIC; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; diff --git a/scripts/verify_primer_class_hash.sh b/scripts/verify_primer_class_hash.sh new file mode 100755 index 0000000..43bd6f6 --- /dev/null +++ b/scripts/verify_primer_class_hash.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +EXPECTED_CLASS_HASH="0x00123e6bc1c14ae9934e933d3f64916a6116dd6b036a922b2b1f0815e0d1d300" + +# Verify Scarb version for reproducible class hash computation +REQUIRED_SCARB_VERSION="2.14.0" +CURRENT_SCARB_VERSION=$(scarb --version | grep -oP 'scarb \K[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown") +echo "Scarb version: $CURRENT_SCARB_VERSION (expected: $REQUIRED_SCARB_VERSION)" +if [ "$CURRENT_SCARB_VERSION" != "$REQUIRED_SCARB_VERSION" ]; then + echo "WARNING: Scarb version mismatch. Class hash may differ." +fi + +# Build with release profile +echo "Building with release profile..." +SCARB_PROFILE=release scarb build + +# Compute class hash using sncast (extract just the hash from output) +echo "Computing Primer class hash..." +SNCAST_OUTPUT=$(sncast utils class-hash --package contracts --contract-name Primer 2>&1) +ACTUAL_CLASS_HASH=$(echo "$SNCAST_OUTPUT" | grep "Class Hash:" | awk '{print $3}') + +# Compare +echo "Expected: $EXPECTED_CLASS_HASH" +echo "Actual: $ACTUAL_CLASS_HASH" + +if [ "$ACTUAL_CLASS_HASH" = "$EXPECTED_CLASS_HASH" ]; then + echo "SUCCESS: Primer class hash matches expected value" + exit 0 +else + echo "FAILURE: Primer class hash mismatch!" + exit 1 +fi diff --git a/strategy_implementation/Scarb.toml b/strategy_implementation/Scarb.toml new file mode 100644 index 0000000..dfcc425 --- /dev/null +++ b/strategy_implementation/Scarb.toml @@ -0,0 +1,39 @@ +[package] +name = "strategy_implementation" +edition.workspace = true +version.workspace = true + +[dependencies] +account_factory = { path = "../account_factory" } +contracts = { path = "../contracts" } +earn_reporter = { path = "../earn_reporter" } +openzeppelin.workspace = true +starknet.workspace = true +starkware_utils.workspace = true + +[dev-dependencies] +assert_macros.workspace = true +snforge_std.workspace = true +starkware_utils_testing.workspace = true +testing_utils = { path = "../testing_utils" } + +[[target.starknet-contract]] +sierra = true + +[[test]] +build-external-contracts = [ + "starkware_utils::erc20::erc20_mocks::DualCaseERC20Mock", + "account_factory::account_factory::AccountFactory", + "contracts::primer::primer::Primer", + "earn_reporter::earn_reporter::EarnReporter", + "testing_utils::dummy_contracts::DummyEthAddressContract", +] +name = "strategy_implementation_unittest" + +[profile.dev.cairo] +unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" + +[scripts] +test = "snforge test" diff --git a/contracts/src/strategy_implementation/avnu_interface.cairo b/strategy_implementation/src/avnu_interface.cairo similarity index 99% rename from contracts/src/strategy_implementation/avnu_interface.cairo rename to strategy_implementation/src/avnu_interface.cairo index ddd055a..31f4545 100644 --- a/contracts/src/strategy_implementation/avnu_interface.cairo +++ b/strategy_implementation/src/avnu_interface.cairo @@ -39,4 +39,3 @@ pub(crate) struct AvnuParameters { pub integrator_fee_recipient: ContractAddress, pub routes: Array, } - diff --git a/contracts/src/strategy_implementation/interface.cairo b/strategy_implementation/src/interface.cairo similarity index 100% rename from contracts/src/strategy_implementation/interface.cairo rename to strategy_implementation/src/interface.cairo diff --git a/contracts/src/known_addresses.cairo b/strategy_implementation/src/known_addresses.cairo similarity index 100% rename from contracts/src/known_addresses.cairo rename to strategy_implementation/src/known_addresses.cairo diff --git a/contracts/src/strategy_implementation.cairo b/strategy_implementation/src/lib.cairo similarity index 59% rename from contracts/src/strategy_implementation.cairo rename to strategy_implementation/src/lib.cairo index 5714928..7a9e190 100644 --- a/contracts/src/strategy_implementation.cairo +++ b/strategy_implementation/src/lib.cairo @@ -1,9 +1,9 @@ pub mod avnu_interface; pub mod interface; +pub(crate) mod known_addresses; pub mod strategy_implementation; - #[cfg(test)] -pub mod test; +pub(crate) mod test; #[cfg(test)] -pub mod test_utils; +pub(crate) mod test_utils; pub mod utils; diff --git a/contracts/src/strategy_implementation/strategy_implementation.cairo b/strategy_implementation/src/strategy_implementation.cairo similarity index 98% rename from contracts/src/strategy_implementation/strategy_implementation.cairo rename to strategy_implementation/src/strategy_implementation.cairo index 69f2a53..c05c55e 100644 --- a/contracts/src/strategy_implementation/strategy_implementation.cairo +++ b/strategy_implementation/src/strategy_implementation.cairo @@ -1,25 +1,13 @@ #[starknet::contract] pub mod StrategyImplementation { use RolesComponent::InternalTrait as RolesInternalTrait; - use contracts::account_factory::account_factory::{ + use account_factory::account_factory::{ IAccountFactoryDispatcher, IAccountFactoryDispatcherTrait, }; - use contracts::earn_reporter::earn_reporter::{ - IEarnReporterDispatcher, IEarnReporterDispatcherTrait, - }; - use contracts::known_addresses::MIDAS_RE7_BTC; - use contracts::strategy_implementation::avnu_interface::AvnuParameters; - use contracts::strategy_implementation::interface::{ - IStrategyImplementation, IStrategyImplementationSafeDispatcher, - IStrategyImplementationSafeDispatcherTrait, - }; - use contracts::strategy_implementation::utils::{ - IERC4626DepositDispatcher, IERC4626DepositDispatcherTrait, Strategy, StrategyTrait, - avnu_multi_route_swap, deserialize_signature, strategy_from_protocol_and_token, - }; use core::num::traits::Zero; use core::panic_with_felt252; use core::traits::Into; + use earn_reporter::earn_reporter::{IEarnReporterDispatcher, IEarnReporterDispatcherTrait}; use openzeppelin::access::accesscontrol::AccessControlComponent; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; @@ -30,6 +18,16 @@ pub mod StrategyImplementation { use starkware_utils::components::replaceability::ReplaceabilityComponent; use starkware_utils::components::replaceability::ReplaceabilityComponent::InternalReplaceabilityTrait; use starkware_utils::components::roles::RolesComponent; + use strategy_implementation::avnu_interface::AvnuParameters; + use strategy_implementation::interface::{ + IStrategyImplementation, IStrategyImplementationSafeDispatcher, + IStrategyImplementationSafeDispatcherTrait, + }; + use strategy_implementation::known_addresses::MIDAS_RE7_BTC; + use strategy_implementation::utils::{ + IERC4626DepositDispatcher, IERC4626DepositDispatcherTrait, Strategy, StrategyTrait, + avnu_multi_route_swap, deserialize_signature, strategy_from_protocol_and_token, + }; component!(path: RolesComponent, storage: roles, event: RolesEvent); component!(path: AccessControlComponent, storage: accesscontrol, event: accesscontrolEvent); diff --git a/contracts/src/strategy_implementation/test.cairo b/strategy_implementation/src/test.cairo similarity index 99% rename from contracts/src/strategy_implementation/test.cairo rename to strategy_implementation/src/test.cairo index e562fe3..d8cc469 100644 --- a/contracts/src/strategy_implementation/test.cairo +++ b/strategy_implementation/src/test.cairo @@ -1,11 +1,3 @@ -use contracts::known_addresses::{ - AVNU_EXCHANGE, ENDUR_TBTC, ENDUR_WBTC, FORGE_YIELDS_WBTC, LBTC, NOON_WBTC, TBTC, TROVES_TBTC, - WBTC, -}; -use contracts::strategy_implementation::interface::{ - IStrategyImplementationDispatcher, IStrategyImplementationDispatcherTrait, - IStrategyImplementationSafeDispatcher, IStrategyImplementationSafeDispatcherTrait, -}; use core::array::ArrayTrait; use core::num::traits::Zero; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; @@ -15,21 +7,30 @@ use starknet::ContractAddress; use starknet::eth_address::EthAddress; use starknet::secp256_trait::Signature; use starkware_utils_testing::test_utils::{assert_panic_with_felt_error, cheat_caller_address_once}; -use crate::strategy_implementation::test_utils::{ +use strategy_implementation::interface::{ + IStrategyImplementationDispatcher, IStrategyImplementationDispatcherTrait, + IStrategyImplementationSafeDispatcher, IStrategyImplementationSafeDispatcherTrait, +}; +use strategy_implementation::known_addresses::{ + AVNU_EXCHANGE, ENDUR_TBTC, ENDUR_WBTC, FORGE_YIELDS_WBTC, LBTC, NOON_WBTC, TBTC, TROVES_TBTC, + WBTC, +}; +use strategy_implementation::test_utils::{ ApplyParameters, IERC4626DepositMintMockDispatcher, IERC4626DepositMintMockDispatcherTrait, apply, assert_apply_failed_with_refund, assert_deposited_event, build_prefunded_apply_parameters_with_amount, build_prefunded_apply_parameters_with_token_address, build_prefunded_avnu, cheat_transfer_and_approve, deploy_4626_failure_mock, deploy_and_prefund_dummy_erc20_at, - deploy_dummy_avnu, deploy_dummy_avnu_failure, deploy_dummy_avnu_false, + deploy_dummy_avnu, deploy_dummy_avnu_failure, deploy_dummy_avnu_false, deploy_earn_reporter, deploy_erc4626_deposit_mint_mock, deploy_mock_erc20_contract_at, dummy_apply_parameters, dummy_apply_parameters_with_protocol, get_account_factory, get_position_owner, serialize_signature, setup_strategy_implementation_test_env, validate_avnu_swap, }; -use crate::strategy_implementation::utils::{ +use strategy_implementation::utils::{ PROTOCOL_ENDUR, PROTOCOL_FORGE_YIELDS, PROTOCOL_NOON, PROTOCOL_TROVES, }; -use crate::test_utils::{APP_GOVERNOR, deploy_earn_reporter, get_event_by_selector}; +use testing_utils::constants::APP_GOVERNOR; +use testing_utils::event_helpers::get_event_by_selector; #[test] diff --git a/contracts/src/strategy_implementation/test_utils.cairo b/strategy_implementation/src/test_utils.cairo similarity index 96% rename from contracts/src/strategy_implementation/test_utils.cairo rename to strategy_implementation/src/test_utils.cairo index 60153b8..9e4d3c0 100644 --- a/contracts/src/strategy_implementation/test_utils.cairo +++ b/strategy_implementation/src/test_utils.cairo @@ -1,15 +1,3 @@ -use contracts::known_addresses::{AVNU_EXCHANGE, MIDAS_RE7_BTC}; -use contracts::strategy_implementation::avnu_interface::{AvnuParameters, Route}; -use contracts::strategy_implementation::interface::{ - IStrategyImplementationDispatcher, IStrategyImplementationDispatcherTrait, -}; -use contracts::strategy_implementation::strategy_implementation::StrategyImplementation::{ - ApplyFailed, Deposited, MultiRouteSwap, PositionOwnerDeployed, -}; -use contracts::strategy_implementation::utils::{ - PROTOCOL_AVNU, PROTOCOL_TROVES, Strategy, StrategyTrait, TokenTrait, deserialize_signature, - strategy_from_protocol_and_token, -}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::cheatcodes::events::Event; use snforge_std::{ContractClassTrait, DeclareResultTrait, TokenImpl}; @@ -21,10 +9,21 @@ use starkware_utils_testing::test_utils::{ assert_expected_event_emitted, cheat_caller_address_once, set_account_as_app_governor, set_account_as_app_role_admin, }; -use crate::test_utils::{ - APP_GOVERNOR, APP_ROLE_ADMIN, GOVERNANCE_ADMIN, eth_address_to_account, get_event_by_selector, - setup_account_factory_test_env, +use strategy_implementation::avnu_interface::{AvnuParameters, Route}; +use strategy_implementation::interface::{ + IStrategyImplementationDispatcher, IStrategyImplementationDispatcherTrait, +}; +use strategy_implementation::known_addresses::{AVNU_EXCHANGE, MIDAS_RE7_BTC}; +use strategy_implementation::strategy_implementation::StrategyImplementation::{ + ApplyFailed, Deposited, MultiRouteSwap, PositionOwnerDeployed, }; +use strategy_implementation::utils::{ + PROTOCOL_AVNU, PROTOCOL_TROVES, Strategy, StrategyTrait, TokenTrait, deserialize_signature, + strategy_from_protocol_and_token, +}; +use testing_utils::account_factory_utils::{eth_address_to_account, setup_account_factory_test_env}; +use testing_utils::constants::{APP_GOVERNOR, APP_ROLE_ADMIN, GOVERNANCE_ADMIN}; +use testing_utils::event_helpers::get_event_by_selector; #[derive(Drop, Copy)] @@ -578,11 +577,11 @@ pub trait IERC4626DepositMintMock { #[starknet::contract] pub mod ERC4626DepositMintMock { - use contracts::strategy_implementation::test_utils::IERC4626DepositMintMock; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use openzeppelin::token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; use starknet::ContractAddress; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use strategy_implementation::test_utils::IERC4626DepositMintMock; component!(path: ERC20Component, storage: erc20, event: ERC20Event); @@ -694,8 +693,8 @@ pub(crate) fn deploy_4626_failure_mock(address_to_deploy_at: ContractAddress) { // ----------------------------------------------------------------------------- #[starknet::contract] pub mod DummyAvnu { - use contracts::strategy_implementation::avnu_interface::AvnuParameters; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use strategy_implementation::avnu_interface::AvnuParameters; #[storage] struct Storage {} @@ -731,8 +730,8 @@ pub(crate) fn deploy_dummy_avnu(address_to_deploy_at: ContractAddress) { // ----------------------------------------------------------------------------- #[starknet::contract] pub mod DummyAvnuFailure { - use contracts::strategy_implementation::avnu_interface::AvnuParameters; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use strategy_implementation::avnu_interface::AvnuParameters; #[storage] struct Storage {} @@ -767,7 +766,7 @@ pub(crate) fn deploy_dummy_avnu_failure(address_to_deploy_at: ContractAddress) { // ----------------------------------------------------------------------------- #[starknet::contract] pub mod DummyAvnuFalse { - use contracts::strategy_implementation::avnu_interface::AvnuParameters; + use strategy_implementation::avnu_interface::AvnuParameters; #[storage] struct Storage {} @@ -784,3 +783,14 @@ pub(crate) fn deploy_dummy_avnu_false(address_to_deploy_at: ContractAddress) { let (addr, _) = cls.deploy_at(@calldata, address_to_deploy_at).unwrap_syscall(); assert!(addr == address_to_deploy_at, "deploy_at failed"); } + +/// Deploy the EarnReporter contract and return its address. +pub(crate) fn deploy_earn_reporter(owner: ContractAddress) -> ContractAddress { + let earn_reporter_class = snforge_std::declare("EarnReporter") + .unwrap_syscall() + .contract_class(); + let (earn_reporter_addr, _) = earn_reporter_class + .deploy(@array![owner.into()]) + .unwrap_syscall(); + earn_reporter_addr +} diff --git a/contracts/src/strategy_implementation/utils.cairo b/strategy_implementation/src/utils.cairo similarity index 96% rename from contracts/src/strategy_implementation/utils.cairo rename to strategy_implementation/src/utils.cairo index 552b48a..d60f721 100644 --- a/contracts/src/strategy_implementation/utils.cairo +++ b/strategy_implementation/src/utils.cairo @@ -1,15 +1,14 @@ -use contracts::known_addresses::AVNU_EXCHANGE; -use contracts::strategy_implementation::avnu_interface::{ - AvnuParameters, IAvnuDispatcher, IAvnuDispatcherTrait, -}; use core::hash::HashStateTrait; use core::panic_with_felt252; use core::pedersen::PedersenTrait; use starknet::ContractAddress; use starknet::secp256_trait::Signature; -use crate::known_addresses::{ - ENDUR_LBTC, ENDUR_SOLVBTC, ENDUR_TBTC, ENDUR_WBTC, FORGE_YIELDS_WBTC, LBTC, NOON_WBTC, SOLVBTC, - TBTC, TROVES_LBTC, TROVES_SOLVBTC, TROVES_TBTC, TROVES_WBTC, WBTC, +use strategy_implementation::avnu_interface::{ + AvnuParameters, IAvnuDispatcher, IAvnuDispatcherTrait, +}; +use strategy_implementation::known_addresses::{ + AVNU_EXCHANGE, ENDUR_LBTC, ENDUR_SOLVBTC, ENDUR_TBTC, ENDUR_WBTC, FORGE_YIELDS_WBTC, LBTC, + NOON_WBTC, SOLVBTC, TBTC, TROVES_LBTC, TROVES_SOLVBTC, TROVES_TBTC, TROVES_WBTC, WBTC, }; diff --git a/testing_utils/Scarb.toml b/testing_utils/Scarb.toml new file mode 100644 index 0000000..cd2ac04 --- /dev/null +++ b/testing_utils/Scarb.toml @@ -0,0 +1,29 @@ +[package] +name = "testing_utils" +edition.workspace = true +version.workspace = true + +[lib] + +[dependencies] +account_factory = { path = "../account_factory" } +contracts = { path = "../contracts" } +openzeppelin.workspace = true +snforge_std.workspace = true +starknet.workspace = true +starkware_utils.workspace = true +starkware_utils_testing.workspace = true + +[[target.starknet-contract]] +sierra = true + +[[test]] +name = "testing_utils_unittest" + +[profile.dev.cairo] +unstable-add-statements-functions-debug-info = true +unstable-add-statements-code-locations-debug-info = true +inlining-strategy = "avoid" + +[scripts] +test = "snforge test" diff --git a/testing_utils/src/account_factory_utils.cairo b/testing_utils/src/account_factory_utils.cairo new file mode 100644 index 0000000..9702527 --- /dev/null +++ b/testing_utils/src/account_factory_utils.cairo @@ -0,0 +1,68 @@ +use account_factory::utils::{PRIMER_CLASS_HASH, compute_contract_address}; +use snforge_std::{ContractClassTrait, DeclareResultTrait}; +use starknet::eth_address::EthAddress; +use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; +use starkware_utils_testing::test_utils::{ + set_account_as_app_governor, set_account_as_app_role_admin, +}; +use testing_utils::constants::{APP_GOVERNOR, APP_ROLE_ADMIN, GOVERNANCE_ADMIN}; +use testing_utils::dummy_contracts::declare_dummy_eth_address_contract; + +/// Mirrors AccountFactory.eth_address_to_account for tests, using the +/// PRIMER_CLASS_HASH and the account factory address as the deployer address. +pub fn eth_address_to_account( + account_factory: ContractAddress, eth_address: EthAddress, +) -> ContractAddress { + compute_contract_address( + salt: eth_address.into(), + class_hash: PRIMER_CLASS_HASH.into(), + constructor_calldata: array![].span(), + deployer_address: account_factory.into(), + ) +} + +/// Declare the `Primer` contract and return its class hash. +pub fn declare_primer_contract() -> ClassHash { + *snforge_std::declare("Primer").unwrap_syscall().contract_class().class_hash +} + +/// Sets default roles for the AccountFactory contract. +pub fn set_account_factory_default_roles(account_factory: ContractAddress) { + // App role admin + set_account_as_app_role_admin( + contract: account_factory, account: APP_ROLE_ADMIN(), governance_admin: GOVERNANCE_ADMIN(), + ); + // App governor (requires app role admin) + set_account_as_app_governor( + contract: account_factory, account: APP_GOVERNOR(), app_role_admin: APP_ROLE_ADMIN(), + ); +} + +/// Builds the constructor calldata array for AccountFactory. +pub fn account_factory_constructor_calldata() -> Array { + let governance_admin: ContractAddress = GOVERNANCE_ADMIN(); + let upgrade_delay: u64 = 0; + let account_class_hash: ClassHash = declare_dummy_eth_address_contract(); + let mut calldata: Array = array![]; + Serde::serialize(@governance_admin, ref calldata); + Serde::serialize(@upgrade_delay, ref calldata); + Serde::serialize(@account_class_hash, ref calldata); + calldata +} + +/// Sets up the AccountFactory test environment: +/// - deploys the `AccountFactory` contract, +/// - sets default roles, +/// - declares the `Primer` contract so its class hash is available. +pub fn setup_account_factory_test_env() -> ContractAddress { + let calldata = account_factory_constructor_calldata(); + let account_factory_contract = snforge_std::declare("AccountFactory") + .unwrap_syscall() + .contract_class(); + let (account_factory_contract_address, _) = account_factory_contract + .deploy(@calldata) + .unwrap_syscall(); + set_account_factory_default_roles(account_factory_contract_address); + declare_primer_contract(); + account_factory_contract_address +} diff --git a/testing_utils/src/constants.cairo b/testing_utils/src/constants.cairo new file mode 100644 index 0000000..3b3695d --- /dev/null +++ b/testing_utils/src/constants.cairo @@ -0,0 +1,13 @@ +use starknet::ContractAddress; + +pub fn APP_ROLE_ADMIN() -> ContractAddress { + 'APP_ROLE_ADMIN'.try_into().unwrap() +} + +pub fn APP_GOVERNOR() -> ContractAddress { + 'APP_GOVERNOR'.try_into().unwrap() +} + +pub fn GOVERNANCE_ADMIN() -> ContractAddress { + 'GOVERNANCE_ADMIN'.try_into().unwrap() +} diff --git a/testing_utils/src/dummy_contracts.cairo b/testing_utils/src/dummy_contracts.cairo new file mode 100644 index 0000000..8d3dd8d --- /dev/null +++ b/testing_utils/src/dummy_contracts.cairo @@ -0,0 +1,51 @@ +use snforge_std::DeclareResultTrait; +use starknet::{ClassHash, SyscallResultTrait}; + +/// Minimal no-op contract for tests: stores an EthAddress passed at construction. +#[starknet::contract] +pub mod DummyEthAddressContract { + use starknet::eth_address::EthAddress; + use starknet::secp256_trait::Signature; + + #[storage] + struct Storage {} + + #[external(v0)] + fn initialize(ref self: ContractState, eth_address: EthAddress, signature: Signature) { + return; + } +} + +/// Declare the `DummyEthAddressContract` contract and return its class hash. +pub fn declare_dummy_eth_address_contract() -> ClassHash { + *snforge_std::declare("DummyEthAddressContract").unwrap_syscall().contract_class().class_hash +} + +/// Second dummy contract with a different class hash for upgrade testing. +#[starknet::contract] +pub mod SecondDummyEthAddressContract { + use starknet::eth_address::EthAddress; + use starknet::secp256_trait::Signature; + + #[storage] + struct Storage {} + + #[constructor] + pub fn constructor(ref self: ContractState) { + // This assert is just to get a different class hash for the contract. + assert!(true, "ERROR"); + } + + #[external(v0)] + fn initialize(ref self: ContractState, eth_address: EthAddress, signature: Signature) { + return; + } +} + +/// Declare the `SecondDummyEthAddressContract` contract and return its class hash. +pub fn declare_second_dummy_eth_address_contract() -> ClassHash { + *snforge_std::declare("SecondDummyEthAddressContract") + .unwrap_syscall() + .contract_class() + .class_hash +} diff --git a/testing_utils/src/event_helpers.cairo b/testing_utils/src/event_helpers.cairo new file mode 100644 index 0000000..5afd11a --- /dev/null +++ b/testing_utils/src/event_helpers.cairo @@ -0,0 +1,46 @@ +use snforge_std::cheatcodes::events::Event; +use starknet::ContractAddress; + +/// Returns the index of the nth event whose first key equals the given selector. +pub fn find_event_index_by_selector( + events: Span<(ContractAddress, Event)>, selector: felt252, n: usize, +) -> Option { + let mut i = 0_usize; + let mut seen = 0_usize; + for (_, ev) in events { + if ev.keys.len() > 0 && *ev.keys.at(0) == selector { + if seen == n { + return Option::Some(i); + } + seen += 1; + } + i += 1; + } + None +} + +/// Returns a cloned copy of the first event emitted with the given selector (if any). +pub fn get_event_by_selector( + events: Span<(ContractAddress, Event)>, selector: felt252, +) -> Option<@(ContractAddress, Event)> { + match find_event_index_by_selector(:events, :selector, n: 0) { + Option::Some(i) => { + let (from, ev) = events.at(i); + Option::Some(@(*from, ev.clone())) + }, + None => None, + } +} + +/// Returns a cloned copy of the nth event emitted with the given selector (if any). +pub fn get_event_by_selector_n( + events: Span<(ContractAddress, Event)>, selector: felt252, n: usize, +) -> Option<@(ContractAddress, Event)> { + match find_event_index_by_selector(:events, :selector, :n) { + Option::Some(i) => { + let (from, ev) = events.at(i); + Option::Some(@(*from, ev.clone())) + }, + None => None, + } +} diff --git a/testing_utils/src/lib.cairo b/testing_utils/src/lib.cairo new file mode 100644 index 0000000..82376c2 --- /dev/null +++ b/testing_utils/src/lib.cairo @@ -0,0 +1,4 @@ +pub mod account_factory_utils; +pub mod constants; +pub mod dummy_contracts; +pub mod event_helpers;