diff --git a/src/chainlib/ChainLib.cairo b/src/chainlib/ChainLib.cairo index a62b810..506baa9 100644 --- a/src/chainlib/ChainLib.cairo +++ b/src/chainlib/ChainLib.cairo @@ -224,6 +224,8 @@ pub mod ChainLib { creator_sales: Map, total_sales_for_content: Map, token_address: ContractAddress, + reentrancy_guard: bool, + is_paused: bool, platform_fee: u256, //basis points; 1000 = 10% platform_fee_recipient: ContractAddress, payout_schedule: PayoutSchedule, @@ -255,6 +257,8 @@ pub mod ChainLib { self.next_purchase_id.write(1_u256); self.next_content_id.write(0_felt252); self.purchase_timeout_duration.write(3600); + self.reentrancy_guard.write(false); + self.is_paused.write(false); self.platform_fee_recipient.write(get_contract_address()); // self.platform_fee.write(PLATFORM_FEE); self.platform_fee.write(platform_fee); @@ -298,6 +302,8 @@ pub mod ChainLib { SubscriptionCancelled: SubscriptionCancelled, SubscriptionRenewed: SubscriptionRenewed, ReceiptGenerated: ReceiptGenerated, + EmergencyPaused: EmergencyPaused, + EmergencyUnpause: EmergencyUnpause, PayoutExecuted: PayoutExecuted, PayoutScheduleSet: PayoutScheduleSet, RefundRequested: RefundRequested, @@ -314,6 +320,19 @@ pub mod ChainLib { pub id: u256, } + + #[derive(Drop, starknet::Event)] + pub struct EmergencyPaused { + pub paused_by: ContractAddress, + pub timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + pub struct EmergencyUnpause { + pub unpaused_by: ContractAddress, + pub timestamp: u64, + } + #[derive(Drop, starknet::Event)] pub struct UserUpdated { pub user_id: u256, @@ -551,6 +570,7 @@ pub mod ChainLib { fn create_token_account( ref self: ContractState, user_name: felt252, init_param1: felt252, init_param2: felt252, ) -> u256 { + self.assert_not_paused(); // Ensure that the username is not empty. assert!(user_name != 0, "User name cannot be empty"); @@ -616,6 +636,7 @@ pub mod ChainLib { fn register_user( ref self: ContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, ) -> u256 { + self.assert_not_paused(); // Ensure that the username is not empty. assert!(username != 0, "User name cannot be empty"); @@ -659,6 +680,7 @@ pub mod ChainLib { rank: Rank, metadata: felt252, ) { + self.assert_not_paused(); assert!(username != 0, "User name cannot be empty"); let zero: ContractAddress = contract_address_const::<0>(); assert!(wallet_address != zero, "Address cannot be zero"); @@ -687,6 +709,7 @@ pub mod ChainLib { } fn deactivate_profile(ref self: ContractState, user_id: u256) -> bool { + self.assert_not_paused(); let mut user = self.users.read(user_id); // Ensure that the caller is the user or has permission to update. let caller = get_caller_address(); @@ -702,6 +725,7 @@ pub mod ChainLib { } fn verify_user(ref self: ContractState, user_id: u256) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Ensure that only an admin can verify users. assert((self.admin.read() == caller), 'Only admin can verify users'); @@ -753,6 +777,7 @@ pub mod ChainLib { operator: ContractAddress, permissions: Permissions, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let account = self.accounts.read(account_id); @@ -776,6 +801,7 @@ pub mod ChainLib { fn revoke_operator( ref self: ContractState, account_id: u256, operator: ContractAddress, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let account = self.accounts.read(account_id); @@ -807,6 +833,7 @@ pub mod ChainLib { fn modify_account_permissions( ref self: ContractState, account_id: u256, permissions: Permissions, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let mut account = self.accounts.read(account_id); @@ -839,6 +866,7 @@ pub mod ChainLib { content_type: ContentType, category: Category, ) -> felt252 { + self.assert_not_paused(); assert!(title != 0, "Title cannot be empty"); assert!(description != 0, "Description cannot be empty"); @@ -883,6 +911,8 @@ pub mod ChainLib { fn process_initial_payment( ref self: ContractState, amount: u256, subscriber: ContractAddress, ) -> bool { + self.assert_not_paused(); + self.start_reentrancy_guard(); // Get the caller's address - this is who is initiating the subscription let caller = get_caller_address(); @@ -971,6 +1001,7 @@ pub mod ChainLib { }, ); + self.end_reentrancy_guard(); true } @@ -978,6 +1009,8 @@ pub mod ChainLib { /// @param subscription_id The unique identifier of the subscription /// @return bool Returns true if the recurring payment is processed successfully fn process_recurring_payment(ref self: ContractState, subscription_id: u256) -> bool { + self.assert_not_paused(); + self.start_reentrancy_guard(); // Get the subscription let mut subscription = self.subscriptions.read(subscription_id); @@ -1045,6 +1078,8 @@ pub mod ChainLib { }, ); + self.end_reentrancy_guard(); + true } @@ -1052,6 +1087,7 @@ pub mod ChainLib { /// @param payment_id The unique identifier of the payment to verify /// @return bool Returns true if the payment is verified successfully fn verify_payment(ref self: ContractState, payment_id: u256) -> bool { + self.assert_not_paused(); // Only admin should be able to verify payments let caller = get_caller_address(); assert(self.admin.read() == caller, 'Only admin can verify payments'); @@ -1087,6 +1123,8 @@ pub mod ChainLib { /// @param subscription_id The unique identifier of the subscription to refund /// @return bool Returns true if the refund is processed successfully fn process_refund(ref self: ContractState, subscription_id: u256) -> bool { + self.assert_not_paused(); + self.start_reentrancy_guard(); // Only admin should be able to process refunds let caller = get_caller_address(); assert(self.admin.read() == caller, 'Only admin can process refunds'); @@ -1135,6 +1173,7 @@ pub mod ChainLib { }, ); + self.end_reentrancy_guard(); true } @@ -1144,6 +1183,7 @@ pub mod ChainLib { content_id: felt252, requirements: Array, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let content = self.content.read(content_id); @@ -1196,6 +1236,7 @@ pub mod ChainLib { fn set_content_access_rules( ref self: ContractState, content_id: felt252, rules: Array, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let content = self.content.read(content_id); @@ -1249,6 +1290,7 @@ pub mod ChainLib { fn add_content_access_rule( ref self: ContractState, content_id: felt252, rule: AccessRule, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let content = self.content.read(content_id); @@ -1302,6 +1344,7 @@ pub mod ChainLib { verification_type: VerificationType, is_verified: bool, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); assert(self.admin.read() == caller, 'Only admin can verify users'); @@ -1339,6 +1382,7 @@ pub mod ChainLib { user: ContractAddress, permissions: Permissions, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let content = self.content.read(content_id); @@ -1380,6 +1424,7 @@ pub mod ChainLib { expiration: u64, max_actions: u64, ) -> bool { + self.assert_not_paused(); // Ensure the delegate address is valid let x: ContractAddress = 0.try_into().unwrap(); assert(delegate != x, 'Invalid delegate address'); @@ -1440,6 +1485,7 @@ pub mod ChainLib { fn revoke_delegation( ref self: ContractState, delegate: ContractAddress, permissions: u64, ) -> bool { + self.assert_not_paused(); // Get the delegator (caller) let delegator = get_caller_address(); @@ -1494,6 +1540,7 @@ pub mod ChainLib { fn use_delegation( ref self: ContractState, delegator: ContractAddress, permission: u64, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Check if the caller has the required permission via delegation @@ -1549,6 +1596,7 @@ pub mod ChainLib { fn create_subscription( ref self: ContractState, user_id: u256, amount: u256, plan_type: u32, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Verify the user exists @@ -1613,6 +1661,7 @@ pub mod ChainLib { fn grant_premium_access( ref self: ContractState, user_id: u256, content_id: felt252, ) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let content = self.content.read(content_id); @@ -1675,6 +1724,7 @@ pub mod ChainLib { } fn revoke_access(ref self: ContractState, user_id: u256, content_id: felt252) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); let content = self.content.read(content_id); @@ -1718,6 +1768,7 @@ pub mod ChainLib { } fn set_cache_ttl(ref self: ContractState, ttl_seconds: u64) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Only admin can set cache TTL @@ -1728,6 +1779,7 @@ pub mod ChainLib { } fn verify_access(ref self: ContractState, user_id: u256, content_id: felt252) -> bool { + self.assert_not_paused(); let current_time = get_block_timestamp(); let cache_key = (user_id, content_id); @@ -1821,6 +1873,7 @@ pub mod ChainLib { } fn initialize_access_control(ref self: ContractState, default_cache_ttl: u64) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Only admin can initialize access control @@ -1832,6 +1885,7 @@ pub mod ChainLib { } fn clear_access_cache(ref self: ContractState, user_id: u256, content_id: felt252) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Only admin can clear cache entries @@ -1853,6 +1907,7 @@ pub mod ChainLib { /// @param content_id The unique identifier of the content. /// @param price The price to set for the content. fn set_content_price(ref self: ContractState, content_id: felt252, price: u256) { + self.assert_not_paused(); // Only admin can set content prices let caller = get_caller_address(); assert(self.admin.read() == caller, 'Admin only'); @@ -1870,6 +1925,9 @@ pub mod ChainLib { fn purchase_content( ref self: ContractState, content_id: felt252, transaction_hash: felt252, ) -> u256 { + self.assert_not_paused(); + self.start_reentrancy_guard(); + // assert!(content_id != 0, "Content ID cannot be empty"); // I commented the above line out because in other parts of the project, it is // explicitly stated that first content id should be 0 @@ -1903,6 +1961,8 @@ pub mod ChainLib { let timestamp = get_block_timestamp(); self.emit(ContentPurchased { purchase_id, content_id, buyer, price, timestamp }); + self.end_reentrancy_guard(); + purchase_id } @@ -1975,6 +2035,7 @@ pub mod ChainLib { /// @param purchase_id The unique identifier of the purchase to verify. /// @return bool True if the purchase is valid and completed, false otherwise. fn verify_purchase(ref self: ContractState, purchase_id: u256) -> bool { + self.assert_not_paused(); // Get the purchase details let purchase = self.purchases.read(purchase_id); // assert() @@ -2060,6 +2121,7 @@ pub mod ChainLib { fn update_purchase_status( ref self: ContractState, purchase_id: u256, status: PurchaseStatus, ) -> bool { + self.assert_not_paused(); // Only admin can update purchase status let caller = get_caller_address(); assert(self.admin.read() == caller, 'Only admin can update status'); @@ -2093,6 +2155,7 @@ pub mod ChainLib { } fn cancel_subscription(ref self: ContractState, user_id: u256) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Verify the user exists @@ -2128,6 +2191,7 @@ pub mod ChainLib { } fn renew_subscription(ref self: ContractState, user_id: u256) -> bool { + self.assert_not_paused(); let caller = get_caller_address(); // Verify the user exists @@ -2183,6 +2247,7 @@ pub mod ChainLib { price: u256, transaction_hash: felt252, ) -> u256 { + self.assert_not_paused(); let receipt_id = self.receipt_counter.read() + 1; let issued_at = starknet::get_block_timestamp(); @@ -2205,6 +2270,25 @@ pub mod ChainLib { receipt_id } + fn emergency_pause(ref self: ContractState) { + let caller = get_caller_address(); + assert((self.admin.read() == caller), 'Only admin can pause'); + + self.is_paused.write(true); + self.emit(EmergencyPaused { paused_by: caller, timestamp: get_block_timestamp() }); + } + fn emergency_unpause(ref self: ContractState) { + let caller = get_caller_address(); + assert((self.admin.read() == caller), 'Only admin can unpause'); + + self.is_paused.write(false); + self.emit(EmergencyUnpause { unpaused_by: caller, timestamp: get_block_timestamp() }); + } + + fn is_paused(self: @ContractState) -> bool { + self.is_paused.read() + } + fn get_receipt(self: @ContractState, receipt_id: u256) -> Receipt { let receipt = self.receipt.read(receipt_id); receipt @@ -2230,6 +2314,7 @@ pub mod ChainLib { } fn batch_payout_creators(ref self: ContractState) { + self.assert_not_paused(); let caller = get_caller_address(); assert(self.admin.read() == caller, 'Only admin can execute payout'); @@ -2347,6 +2432,7 @@ pub mod ChainLib { fn approve_refund( ref self: ContractState, refund_id: u64, user_id: u256, refund_percentage: Option, ) { + self.assert_not_paused(); let caller = get_caller_address(); // Ensure that only an admin can verify users. assert((self.admin.read() == caller), 'Only admin can approve refunds'); @@ -2415,6 +2501,7 @@ pub mod ChainLib { } fn decline_refund(ref self: ContractState, refund_id: u64, user_id: u256) { + self.assert_not_paused(); let caller = get_caller_address(); // Ensure that only an admin can verify users. assert((self.admin.read() == caller), 'Only admin can approve refunds'); @@ -2442,6 +2529,7 @@ pub mod ChainLib { } fn refund_user(ref self: ContractState, refund_id: u64, user_id: u256) { + self.assert_not_paused(); let caller = get_caller_address(); // Ensure that only an admin can verify users. assert((self.admin.read() == caller), 'Only admin can approve refunds'); @@ -2511,6 +2599,7 @@ pub mod ChainLib { } fn set_platform_fee(ref self: ContractState, platform_fee: u256) { + self.assert_not_paused(); let caller = get_caller_address(); // Ensure that only an admin can verify users. assert((self.admin.read() == caller), 'Only admin can execute refunds'); @@ -2522,6 +2611,7 @@ pub mod ChainLib { } fn set_refund_window(ref self: ContractState, window: u64) { + self.assert_not_paused(); let caller = get_caller_address(); // Ensure that only an admin can verify users. assert((self.admin.read() == caller), 'Only admin can execute refunds'); @@ -2538,6 +2628,7 @@ pub mod ChainLib { /// @param amount The amount of tokens to transfer. /// @require The caller must have sufficient token allowance and balance. fn _process_payment(ref self: ContractState, amount: u256) { + self.assert_not_paused(); let strk_token = IERC20Dispatcher { contract_address: self.token_address.read() }; let caller = get_caller_address(); let contract_address = get_contract_address(); @@ -2582,12 +2673,33 @@ pub mod ChainLib { } fn _process_refund(ref self: ContractState, amount: u256, refund_address: ContractAddress) { + self.assert_not_paused(); let token = IERC20Dispatcher { contract_address: self.token_address.read() }; let contract_address = get_contract_address(); self._check_token_balance(contract_address, amount); token.transfer(refund_address, amount); } + /// @notice starts the re entrancy guard + /// @dev asserts the reentrancy guard has not been started already + fn start_reentrancy_guard(ref self: ContractState) { + assert(!self.reentrancy_guard.read(), 'Reentrant call'); + + self.reentrancy_guard.write(true); + } + + /// @notice ends the re entrancy guard + /// @dev writes false to the reentrancy guard + fn end_reentrancy_guard(ref self: ContractState) { + self.reentrancy_guard.write(false); + } + + /// @notice asserts that the function is not paused + /// @dev reads the is paused storage and throws an error if the is paused variable is true + fn assert_not_paused(self: @ContractState) { + assert(!self.is_paused.read(), 'Contract is paused'); + } + fn _get_refund_percentage( ref self: ContractState, refund_reason: RefundRequestReason, ) -> u256 { diff --git a/src/interfaces/IChainLib.cairo b/src/interfaces/IChainLib.cairo index 5b89feb..72d89d5 100644 --- a/src/interfaces/IChainLib.cairo +++ b/src/interfaces/IChainLib.cairo @@ -227,6 +227,9 @@ pub trait IChainLib { fn is_receipt_valid(self: @TContractState, receipt_id: u256) -> bool; fn get_total_sales_by_creator(self: @TContractState, creator: ContractAddress) -> u256; fn get_total_sales_for_content(self: @TContractState, content_id: felt252) -> u256; + fn emergency_pause(ref self: TContractState); + fn emergency_unpause(ref self: TContractState); + fn is_paused(self: @TContractState) -> bool; // fn get_daily_sales(self: @TContractState, day: u64) -> u256; // fn get_unique_buyers_count(self: @TContractState) -> u256; diff --git a/tests/test_ChainLib.cairo b/tests/test_ChainLib.cairo index 78bf45d..810264a 100644 --- a/tests/test_ChainLib.cairo +++ b/tests/test_ChainLib.cairo @@ -1,3 +1,4 @@ +use ChainLib::ChainLib::{EmergencyPaused, EmergencyUnpause}; // Import the contract modules use chain_lib::base::types::{ PurchaseStatus, Rank, Refund, RefundRequestReason, RefundStatus, Role, Status, @@ -7,14 +8,14 @@ use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentType, PlanType, S use chain_lib::interfaces::IChainLib::{IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::{ - CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_block_timestamp, cheat_caller_address, - declare, start_cheat_block_timestamp, start_cheat_caller_address, stop_cheat_block_timestamp, - stop_cheat_caller_address, + CheatSpan, ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, + cheat_block_timestamp, cheat_caller_address, declare, spy_events, start_cheat_block_timestamp, + start_cheat_caller_address, stop_cheat_block_timestamp, stop_cheat_caller_address, }; -use starknet::ContractAddress; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; use starknet::testing::{set_caller_address, set_contract_address}; +use starknet::{ContractAddress, get_block_timestamp}; use crate::test_utils::{setup, setup_content_with_price, token_faucet_and_allowance}; #[test] @@ -80,6 +81,25 @@ fn test_create_user() { assert(!user.verified, 'already verified'); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_create_user_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + dispatcher.register_user(username, role.clone(), rank.clone(), metadata); +} + #[test] fn test_verify_user() { let (contract_address, admin_address, erc20_address) = setup(); @@ -156,6 +176,54 @@ fn test_update_user() { } +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_update_user_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call create_user + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + // Validate that the claim ID is correctly incremented + assert(account_id == 0, 'account_id should start from 0'); + + // Retrieve the user to verify it was stored correctly + let user = dispatcher.retrieve_user_profile(account_id); + assert(user.username == username, 'username mismatch'); + assert(user.role == role, 'role mismatch'); + assert(user.rank == rank, 'rank mismatch'); + assert(user.metadata == metadata, 'metadata mismatch'); + assert(!user.verified, 'already verified'); + + let updated_wallet_Address = contract_address_const::<'user'>(); + let updated_username: felt252 = 'John'; + let updated_metadata: felt252 = 'john is a man now'; + let updated_role = Role::READER; + let updated_rank = Rank::BEGINNER; + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Update user data + dispatcher + .update_user_profile( + account_id, + updated_username, + updated_wallet_Address, + updated_role, + updated_rank, + updated_metadata, + ); +} + #[test] fn test_deactivate_user() { let (contract_address, _, erc20_address) = setup(); @@ -186,6 +254,40 @@ fn test_deactivate_user() { assert(user_deactivated.status == Status::DEACTIVATED, 'User should be deactivated'); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_deactivate_user_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call create_user + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + // Validate that the claim ID is correctly incremented + assert(account_id == 0, 'account_id should start from 0'); + + // Retrieve the user to verify it was stored correctly + let user = dispatcher.retrieve_user_profile(account_id); + assert(user.username == username, 'username mismatch'); + assert(user.role == role, 'role mismatch'); + assert(user.rank == rank, 'rank mismatch'); + assert(user.metadata == metadata, 'metadata mismatch'); + assert(!user.verified, 'already verified'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.deactivate_profile(account_id); +} + #[test] #[should_panic] fn test_deactivate_user_should_panic_if_diffrent_Address() { @@ -309,6 +411,29 @@ fn test_create_subscription() { assert(subscription_record.len() == 1, 'record should have length 1'); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_create_subscription_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call register + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.create_subscription(account_id, 500, 1); +} + #[test] #[should_panic(expected: 'User does not exist')] fn test_create_subscription_invalid_user() { @@ -356,6 +481,35 @@ fn test_grant_premium_access_test_admin() { assert(access, 'Access granted'); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_grant_premium_access_should_panic_if_contract_paused() { + let (contract_address, admin, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::WRITER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + let title: felt252 = 'My Content'; + let description: felt252 = 'This is a test content'; + let content_type: ContentType = ContentType::Text; + let category: Category = Category::Education; + + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + let content_id = dispatcher.register_content(title, description, content_type, category); + + start_cheat_caller_address(contract_address, admin); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.grant_premium_access(account_id, content_id); +} + + #[test] #[should_panic(expected: "Only WRITER can post content")] fn test_is_in_blacklist() { @@ -723,6 +877,7 @@ fn test_verify_purchase() { assert(receipt.purchase_id == purchase_id, 'receipt error'); } + #[test] fn test_update_purchase_status() { let (contract_address, admin_address, erc20_address) = setup(); @@ -853,6 +1008,7 @@ fn test_cancel_subscription() { assert(subscription_record.len() == 1, 'record should have length 1'); } + #[test] fn test_renew_subscription() { let (contract_address, _, erc20_address) = setup(); @@ -892,6 +1048,7 @@ fn test_renew_subscription() { assert(subscription_record.len() == 1, 'record should have length 1'); } + #[test] fn test_batch_payout_creators() { let (contract_address, admin_address, erc20_address) = setup(); @@ -1203,6 +1360,7 @@ fn test_refund_flow_approve_refund_request() { assert(creator_3_new_bal > creator_3_init_bal, 'Failed to credit creator3'); } + #[test] #[should_panic(expected: 'Refund request declined')] fn test_refund_flow_decline_refund_request() { @@ -1429,7 +1587,7 @@ fn test_refund_flow_refund_request_timed_out() { let creator1_content_id: felt252 = dispatcher .register_content(title1, description, content_type, category); stop_cheat_caller_address(contract_address); - + println!("creator1_content_id: {}", creator1_content_id); start_cheat_caller_address(contract_address, creator_2); let creator2_content_id: felt252 = dispatcher .register_content(title2, description, content_type, category); @@ -1534,3 +1692,493 @@ fn test_refund_flow_refund_request_timed_out() { assert(creator_2_new_bal > creator_2_init_bal, 'Failed to credit creator2'); assert(creator_3_new_bal > creator_3_init_bal, 'Failed to credit creator3'); } + + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_cancel_subscription_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call register + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + dispatcher.create_subscription(account_id, 500, 1); + let subscription = dispatcher.get_user_subscription(account_id); + assert(subscription.id == 1, 'Subscription ID should be 1'); + assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY'); + assert(subscription.status == SubscriptionStatus::Active, 'Plan type should be YEARLY'); + let subscription_record = dispatcher.get_user_subscription_record(account_id); + assert(subscription_record.len() == 1, 'record should have length 1'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.cancel_subscription(account_id); +} + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_renew_subscription_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call register + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + dispatcher.create_subscription(account_id, 500, 1); + let subscription = dispatcher.get_user_subscription(account_id); + assert(subscription.id == 1, 'Subscription ID should be 1'); + assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY'); + assert(subscription.status == SubscriptionStatus::Active, 'Plan type should be YEARLY'); + let subscription_record = dispatcher.get_user_subscription_record(account_id); + assert(subscription_record.len() == 1, 'record should have length 1'); + + dispatcher.cancel_subscription(account_id); + let subscription = dispatcher.get_user_subscription(account_id); + assert(subscription.id == 1, 'Subscription ID should be 1'); + assert(subscription.subscription_type == PlanType::YEARLY, 'Plan type should be YEARLY'); + assert(subscription.status == SubscriptionStatus::Cancelled, 'Plan status should be cancelled'); + let subscription_record = dispatcher.get_user_subscription_record(account_id); + assert(subscription_record.len() == 1, 'record should have length 1'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.renew_subscription(account_id); +} + + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_refund_flow_refund_user_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let user_address = contract_address_const::<'user'>(); + let creator_1 = contract_address_const::<'creator_1'>(); + let creator_2 = contract_address_const::<'creator_2'>(); + let creator_3 = contract_address_const::<'creator_3'>(); + + let username1: felt252 = 'creator_1'; + let username2: felt252 = 'creator_2'; + let username3: felt252 = 'creator_3'; + let user_name: felt252 = 'user'; + + let role: Role = Role::WRITER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + start_cheat_caller_address(contract_address, user_address); + let user_id = dispatcher.register_user(user_name, Role::READER, Rank::BEGINNER, metadata); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_1); + let creator1_id = dispatcher.register_user(username1, role.clone(), rank.clone(), metadata); + println!("successfully registered creator_1 as writer"); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_2); + let creator2_id = dispatcher.register_user(username2, role.clone(), rank.clone(), metadata); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_3); + let creator3_id = dispatcher.register_user(username3, role.clone(), rank.clone(), metadata); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, admin_address); + let is_creator_1_verified = dispatcher.verify_user(creator1_id); + let is_creator_2_verified = dispatcher.verify_user(creator2_id); + let is_creator_3_verified = dispatcher.verify_user(creator3_id); + stop_cheat_caller_address(contract_address); + + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let creator_1_init_bal = erc20_dispatcher.balance_of(creator_1); + let creator_2_init_bal = erc20_dispatcher.balance_of(creator_2); + let creator_3_init_bal = erc20_dispatcher.balance_of(creator_3); + + token_faucet_and_allowance(dispatcher, user_address, erc20_address, 100000); + // Set up test data + let title1: felt252 = 'Creator 1 content'; + let title2: felt252 = 'Creator 2 content'; + let title3: felt252 = 'Creator 3 content'; + + let description: felt252 = 'This is a test content'; + let content_type: ContentType = ContentType::Text; + let category: Category = Category::Education; + + start_cheat_caller_address(contract_address, creator_1); + let creator1_content_id: felt252 = dispatcher + .register_content(title1, description, content_type, category); + stop_cheat_caller_address(contract_address); + println!("creator1_content_id: {}", creator1_content_id); + start_cheat_caller_address(contract_address, creator_2); + let creator2_content_id: felt252 = dispatcher + .register_content(title2, description, content_type, category); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_3); + let creator3_content_id: felt252 = dispatcher + .register_content(title3, description, content_type, category); + stop_cheat_caller_address(contract_address); + let price_1: u256 = 1000_u256; + let price_2: u256 = 2000_u256; + let price_3: u256 = 1500_u256; + + // Set creator_1 as caller to set up content price + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + // Set up content with price + setup_content_with_price( + dispatcher, admin_address, contract_address, creator1_content_id, price_1, + ); + + // Set up content with price + setup_content_with_price( + dispatcher, admin_address, contract_address, creator2_content_id, price_2, + ); + + // Set up content with price + setup_content_with_price( + dispatcher, admin_address, contract_address, creator3_content_id, price_3, + ); + + // Set user as content consumer + cheat_caller_address(contract_address, user_address, CheatSpan::Indefinite); + + // Purchase the content + let purchase_id_1 = dispatcher.purchase_content(creator1_content_id, 'tx1'); + let purchase_id_2 = dispatcher.purchase_content(creator2_content_id, 'tx2'); + let purchase_id_3 = dispatcher.purchase_content(creator3_content_id, 'tx3'); + + // Initially, purchase should not be verified (status is Pending) + let is_purchase_1_verified = dispatcher.verify_purchase(purchase_id_1); + let is_purchase_2_verified = dispatcher.verify_purchase(purchase_id_2); + let is_purchase_3_verified = dispatcher.verify_purchase(purchase_id_3); + assert(!is_purchase_1_verified, '1 should not be verified'); + assert(!is_purchase_2_verified, '2 should not be verified'); + assert(!is_purchase_3_verified, '3 should not be verified'); + + // Set admin as caller to update the purchase status + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + cheat_block_timestamp(contract_address, 0, CheatSpan::Indefinite); + + // Update purchase status to Completed + let update_result_1 = dispatcher + .update_purchase_status(purchase_id_1, PurchaseStatus::Completed); + assert(update_result_1, 'Failed to update status1'); + let update_result_2 = dispatcher + .update_purchase_status(purchase_id_2, PurchaseStatus::Completed); + assert(update_result_2, 'Failed to update status1'); + let update_result_3 = dispatcher + .update_purchase_status(purchase_id_3, PurchaseStatus::Completed); + assert(update_result_3, 'Failed to update status1'); + + // Now the purchase should be verified + let is_now_verified1 = dispatcher.verify_purchase(purchase_id_1); + assert(is_now_verified1, 'Purchase should be verified'); + let is_now_verified2 = dispatcher.verify_purchase(purchase_id_2); + assert(is_now_verified2, 'Purchase should be verified'); + let is_now_verified3 = dispatcher.verify_purchase(purchase_id_3); + assert(is_now_verified3, 'Purchase should be verified'); + + let receipt = dispatcher.get_receipt(1); + + assert(receipt.purchase_id == purchase_id_1, 'receipt error'); + + start_cheat_caller_address(contract_address, user_address); + start_cheat_block_timestamp(contract_address, 86400); + dispatcher.request_refund(purchase_id_1, RefundRequestReason::MISREPRESENTED_CONTENT); + stop_cheat_block_timestamp(contract_address); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, admin_address); + let refunds_array = dispatcher.get_user_refunds(user_id); + let refund_request = refunds_array.at(0); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.decline_refund(*refund_request.refund_id, user_id); +} + + +#[test] +fn test_contract_pause_and_unpause() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + let is_paused = dispatcher.is_paused(); + assert(is_paused, 'Contract should be paused'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_unpause(); + stop_cheat_caller_address(contract_address); + + let is_unpaused = dispatcher.is_paused(); + assert(!is_unpaused, 'Contract should be unpaused'); +} + +#[test] +fn test_contract_pause_event() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let mut spy = spy_events(); + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + spy + .assert_emitted( + @array![ + ( + contract_address, + chain_lib::chainlib::ChainLib::ChainLib::Event::EmergencyPaused( + EmergencyPaused { + paused_by: admin_address, timestamp: get_block_timestamp(), + }, + ), + ), + ], + ); +} + + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_verify_purchase_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let user_address = contract_address_const::<'user'>(); + + token_faucet_and_allowance(dispatcher, user_address, erc20_address, 100000); + // Set up test data + let content_id: felt252 = 'content1'; + let price: u256 = 1000_u256; + + // Set admin as caller to set up content price + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + + // Set up content with price + setup_content_with_price(dispatcher, admin_address, contract_address, content_id, price); + + // Set user as caller + cheat_caller_address(contract_address, user_address, CheatSpan::Indefinite); + + // Purchase the content + let purchase_id = dispatcher.purchase_content(content_id, 'tx1'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Initially, purchase should not be verified (status is Pending) + let is_verified = dispatcher.verify_purchase(purchase_id); + assert(!is_verified, 'Purchase should not be verified'); +} + + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_update_purchase_status_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let user_address = contract_address_const::<'user'>(); + + token_faucet_and_allowance(dispatcher, user_address, erc20_address, 100000); + // Set up test data + let content_id: felt252 = 'content1'; + let price: u256 = 1000_u256; + + // Set admin as caller to set up content price + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + + // Set up content with price + setup_content_with_price(dispatcher, admin_address, contract_address, content_id, price); + + // Set user as caller + cheat_caller_address(contract_address, user_address, CheatSpan::Indefinite); + + // Purchase the content + let purchase_id = dispatcher.purchase_content(content_id, 'tx1'); + + // Initially, purchase should not be verified (status is Pending) + let is_verified = dispatcher.verify_purchase(purchase_id); + assert(!is_verified, 'Purchase should not be verified'); + + // Set admin as caller to update the purchase status + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Update purchase status to Completed + let update_result = dispatcher.update_purchase_status(purchase_id, PurchaseStatus::Completed); + assert(update_result, 'Failed to update status'); + + // Now the purchase should be verified + let is_now_verified = dispatcher.verify_purchase(purchase_id); + assert(is_now_verified, 'Purchase should be verified'); + + let receipt = dispatcher.get_receipt(1); + + assert(receipt.purchase_id == purchase_id, 'receipt error'); +} + + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_batch_payout_creators_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let user_address = contract_address_const::<'user'>(); + let creator_1 = contract_address_const::<'creator_1'>(); + let creator_2 = contract_address_const::<'creator_2'>(); + let creator_3 = contract_address_const::<'creator_3'>(); + + let username1: felt252 = 'creator_1'; + let username2: felt252 = 'creator_2'; + let username3: felt252 = 'creator_3'; + + let role: Role = Role::WRITER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + start_cheat_caller_address(contract_address, creator_1); + let creator1_id = dispatcher.register_user(username1, role.clone(), rank.clone(), metadata); + println!("successfully registered creator_1 as writer"); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_2); + let creator2_id = dispatcher.register_user(username2, role.clone(), rank.clone(), metadata); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_3); + let creator3_id = dispatcher.register_user(username3, role.clone(), rank.clone(), metadata); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, admin_address); + let is_creator_1_verified = dispatcher.verify_user(creator1_id); + let is_creator_2_verified = dispatcher.verify_user(creator2_id); + let is_creator_3_verified = dispatcher.verify_user(creator3_id); + stop_cheat_caller_address(contract_address); + + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let creator_1_init_bal = erc20_dispatcher.balance_of(creator_1); + let creator_2_init_bal = erc20_dispatcher.balance_of(creator_2); + let creator_3_init_bal = erc20_dispatcher.balance_of(creator_3); + + token_faucet_and_allowance(dispatcher, user_address, erc20_address, 100000); + // Set up test data + let title1: felt252 = 'Creator 1 content'; + let title2: felt252 = 'Creator 2 content'; + let title3: felt252 = 'Creator 3 content'; + + let description: felt252 = 'This is a test content'; + let content_type: ContentType = ContentType::Text; + let category: Category = Category::Education; + + start_cheat_caller_address(contract_address, creator_1); + let creator1_content_id: felt252 = dispatcher + .register_content(title1, description, content_type, category); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_2); + let creator2_content_id: felt252 = dispatcher + .register_content(title2, description, content_type, category); + stop_cheat_caller_address(contract_address); + + start_cheat_caller_address(contract_address, creator_3); + let creator3_content_id: felt252 = dispatcher + .register_content(title3, description, content_type, category); + stop_cheat_caller_address(contract_address); + let price_1: u256 = 1000_u256; + let price_2: u256 = 2000_u256; + let price_3: u256 = 1500_u256; + + // Set creator_1 as caller to set up content price + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + // Set up content with price + setup_content_with_price( + dispatcher, admin_address, contract_address, creator1_content_id, price_1, + ); + + // Set up content with price + setup_content_with_price( + dispatcher, admin_address, contract_address, creator2_content_id, price_2, + ); + + // Set up content with price + setup_content_with_price( + dispatcher, admin_address, contract_address, creator3_content_id, price_3, + ); + + // Set user as content consumer + cheat_caller_address(contract_address, user_address, CheatSpan::Indefinite); + + // Purchase the content + let purchase_id_1 = dispatcher.purchase_content(creator1_content_id, 'tx1'); + let purchase_id_2 = dispatcher.purchase_content(creator2_content_id, 'tx2'); + let purchase_id_3 = dispatcher.purchase_content(creator3_content_id, 'tx3'); + + // Initially, purchase should not be verified (status is Pending) + let is_purchase_1_verified = dispatcher.verify_purchase(purchase_id_1); + let is_purchase_2_verified = dispatcher.verify_purchase(purchase_id_2); + let is_purchase_3_verified = dispatcher.verify_purchase(purchase_id_3); + assert(!is_purchase_1_verified, '1 should not be verified'); + assert(!is_purchase_2_verified, '2 should not be verified'); + assert(!is_purchase_3_verified, '3 should not be verified'); + + // Set admin as caller to update the purchase status + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + cheat_block_timestamp(contract_address, 0, CheatSpan::Indefinite); + + // Update purchase status to Completed + let update_result_1 = dispatcher + .update_purchase_status(purchase_id_1, PurchaseStatus::Completed); + assert(update_result_1, 'Failed to update status1'); + let update_result_2 = dispatcher + .update_purchase_status(purchase_id_2, PurchaseStatus::Completed); + assert(update_result_2, 'Failed to update status1'); + let update_result_3 = dispatcher + .update_purchase_status(purchase_id_3, PurchaseStatus::Completed); + assert(update_result_3, 'Failed to update status1'); + + // Now the purchase should be verified + let is_now_verified1 = dispatcher.verify_purchase(purchase_id_1); + assert(is_now_verified1, 'Purchase should be verified'); + let is_now_verified2 = dispatcher.verify_purchase(purchase_id_2); + assert(is_now_verified2, 'Purchase should be verified'); + let is_now_verified3 = dispatcher.verify_purchase(purchase_id_3); + assert(is_now_verified3, 'Purchase should be verified'); + + let receipt = dispatcher.get_receipt(1); + + assert(receipt.purchase_id == purchase_id_1, 'receipt error'); + + // Still admin calling + cheat_block_timestamp(contract_address, 86400 * 8, CheatSpan::Indefinite); + // start_cheat_block_timestamp(contract_address); + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + dispatcher.batch_payout_creators(); + stop_cheat_caller_address(contract_address); +} + diff --git a/tests/test_account_delegation.cairo b/tests/test_account_delegation.cairo index 30b17e3..40154c9 100644 --- a/tests/test_account_delegation.cairo +++ b/tests/test_account_delegation.cairo @@ -74,6 +74,36 @@ fn test_create_delegation() { } +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_create_delegation_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let contract_instance = IChainLibDispatcher { contract_address }; + + // Setup addresses + let owner = contract_address_const::<'OWNER'>(); + let delegate = contract_address_const::<'DELEGATE'>(); + + // Set owner as caller + start_cheat_caller_address(contract_address, owner); + + // Set current time + let current_time: u64 = 1000; + start_cheat_block_timestamp(contract_address, current_time); + let expiration: u64 = current_time + 3600; // 1 hour in the future + let max_actions: u64 = 5; + + start_cheat_caller_address(contract_address, admin_address); + contract_instance.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Create delegation + contract_instance.create_delegation(delegate, PERMISSION_TRANSFER, expiration, max_actions); + + stop_cheat_caller_address(owner); + stop_cheat_block_timestamp(contract_address); +} + #[test] #[should_panic(expected: 'Invalid delegate address')] fn test_create_delegation_zero_address() { @@ -143,6 +173,40 @@ fn test_revoke_delegation() { stop_cheat_block_timestamp(contract_address); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_revoke_delegation_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let contract_instance = IChainLibDispatcher { contract_address }; + + // Setup addresses + let owner = contract_address_const::<'OWNER'>(); + let delegate = contract_address_const::<'DELEGATE'>(); + + // Set owner as caller + start_cheat_caller_address(contract_address, owner); + + // Set current time + let current_time: u64 = 1000; + start_cheat_block_timestamp(contract_address, current_time); + let expiry: u64 = current_time + 3600; + + // Create delegation first + contract_instance.create_delegation(delegate, PERMISSION_SIGN, expiry, 0); + + start_cheat_caller_address(contract_address, admin_address); + contract_instance.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Revoke delegation + contract_instance.revoke_delegation(delegate, PERMISSION_SIGN); + + stop_cheat_caller_address(owner); + stop_cheat_block_timestamp(contract_address); +} + + #[test] #[should_panic(expected: 'Delegate mismatch')] fn test_revoke_delegation_wrong_delegate() { @@ -240,6 +304,44 @@ fn test_use_delegation() { stop_cheat_block_timestamp(contract_address); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_use_delegation_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let contract_instance = IChainLibDispatcher { contract_address }; + + // Setup addresses + let owner = contract_address_const::<'OWNER'>(); + let delegate = contract_address_const::<'DELEGATE'>(); + + // Set current time + let current_time: u64 = 1000; + start_cheat_block_timestamp(contract_address, current_time); + let expiry: u64 = current_time + 3600; + + // Set owner as caller to create delegation + start_cheat_caller_address(contract_address, owner); + + // Create delegation with max actions + let max_actions: u64 = 3; + contract_instance.create_delegation(delegate, PERMISSION_CALL, expiry, max_actions); + + // Switch caller to delegate + start_cheat_caller_address(contract_address, delegate); + + start_cheat_caller_address(contract_address, admin_address); + contract_instance.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Use delegation + contract_instance.use_delegation(owner, PERMISSION_CALL); + + stop_cheat_caller_address(delegate); + stop_cheat_block_timestamp(contract_address); +} + + #[test] #[should_panic(expected: 'Permission denied')] fn test_use_delegation_exceed_max_actions() { diff --git a/tests/test_contentaccess.cairo b/tests/test_contentaccess.cairo index 9a76038..b667c7f 100644 --- a/tests/test_contentaccess.cairo +++ b/tests/test_contentaccess.cairo @@ -13,6 +13,7 @@ mod permission_tests { use core::option::OptionTrait; use snforge_std::{ CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, + start_cheat_caller_address, stop_cheat_caller_address, }; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; @@ -73,6 +74,61 @@ mod permission_tests { assert(*updated_rules[2].access_type == AccessType::Admin, 'New rule incorrect'); } + + #[test] + #[should_panic(expected: 'Contract is paused')] + fn test_content_access_rules_should_panic_if_contract_paused() { + let (contract_address, _, _) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let content_id = 123; + let user = contract_address_const::<'user'>(); + let admin = contract_address_const::<'admin'>(); + + // Test set_content_access_rules + let mut rules = ArrayTrait::new(); + rules + .append( + AccessRule { + access_type: AccessType::View, + permission_level: 1, + conditions: Option::None, + expires_at: 0, + }, + ); + rules + .append( + AccessRule { + access_type: AccessType::Edit, + permission_level: 2, + conditions: Option::None, + expires_at: 0, + }, + ); + + // Only admin or creator can set rules + cheat_caller_address(contract_address, admin, CheatSpan::Indefinite); + assert!(dispatcher.set_content_access_rules(content_id, rules), "Failed to set rules"); + + // Test get_content_access_rules + let retrieved_rules = dispatcher.get_content_access_rules(content_id); + assert(retrieved_rules.len() == 2, 'Incorrect number of rules'); + assert(*retrieved_rules[0].access_type == AccessType::View, 'First rule incorrect'); + assert(*retrieved_rules[1].access_type == AccessType::Edit, 'Second rule incorrect'); + + // Test add_content_access_rule + let new_rule = AccessRule { + access_type: AccessType::Admin, + permission_level: 3, + conditions: Option::None, + expires_at: 0, + }; + start_cheat_caller_address(contract_address, admin); + dispatcher.emergency_pause(); + dispatcher.add_content_access_rule(content_id, new_rule); + stop_cheat_caller_address(contract_address); + } + + #[test] fn test_verification_workflow() { let (contract_address, admin_address, _) = setup(); @@ -140,6 +196,36 @@ mod permission_tests { ); } + + #[test] + #[should_panic(expected: 'Contract is paused')] + fn test_verification_should_panic_if_contract_paused() { + let (contract_address, admin_address, _) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let content_id = 456; + let user = contract_address_const::<0x03>(); + + // Test set_verification_requirements + let mut requirements = ArrayTrait::new(); + requirements + .append( + VerificationRequirement { + requirement_type: VerificationType::Identity, valid_until: 0, threshold: 1, + }, + ); + requirements + .append( + VerificationRequirement { + requirement_type: VerificationType::Payment, valid_until: 0, threshold: 1, + }, + ); + + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + dispatcher.emergency_pause(); + + dispatcher.set_verification_requirements(content_id, requirements); + } + #[test] fn test_edge_cases() { let (contract_address, admin_address, _) = setup(); diff --git a/tests/test_contentpost.cairo b/tests/test_contentpost.cairo index dcb555c..9cce075 100644 --- a/tests/test_contentpost.cairo +++ b/tests/test_contentpost.cairo @@ -4,7 +4,8 @@ use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentMetadata, Content use chain_lib::interfaces::IChainLib::{IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait}; use snforge_std::{ CheatSpan, ContractClassTrait, DeclareResultTrait, EventSpy, EventSpyAssertionsTrait, - cheat_caller_address, declare, spy_events, + cheat_caller_address, declare, spy_events, start_cheat_caller_address, + stop_cheat_caller_address, }; use starknet::ContractAddress; use starknet::class_hash::ClassHash; @@ -74,6 +75,43 @@ fn test_register_content() { ) } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_register_content_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + + let dispatcher = IChainLibDispatcher { contract_address }; + + let title: felt252 = 'My Content'; + let description: felt252 = 'This is a test content'; + let content_type: ContentType = ContentType::Text; + let category: Category = Category::Education; + let caller_address: ContractAddress = contract_address_const::<'creator'>(); + + // Register a user with WRITER role + let username: felt252 = 'John'; + let role: Role = Role::WRITER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Set caller address for user registration + cheat_caller_address(contract_address, caller_address, CheatSpan::Indefinite); + + // Call register_user + let user_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + // Verify user registration + let user = dispatcher.retrieve_user_profile(user_id); + assert(user.role == Role::WRITER, 'User role not WRITER'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + // Register content + dispatcher.register_content(title, description, content_type, category); +} + #[test] fn test_register_content_with_different_types() { let (contract_address, admin_address, erc20_address) = setup(); diff --git a/tests/test_permissions.cairo b/tests/test_permissions.cairo index 735faaf..8cc344e 100644 --- a/tests/test_permissions.cairo +++ b/tests/test_permissions.cairo @@ -7,6 +7,7 @@ mod permission_tests { }; use snforge_std::{ CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, + start_cheat_caller_address, stop_cheat_caller_address, }; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; @@ -33,6 +34,20 @@ mod permission_tests { assert(token_account.owner_permissions.value == permission_flags::FULL, 'wrong perm'); } + #[test] + #[should_panic(expected: 'Contract is paused')] + fn test_token_account_owner_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + let user_name: felt252 = 'Alice'; + let init_param1: felt252 = 'alice@mail.com'; + let init_param2: felt252 = 'alice profile'; + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + dispatcher.create_token_account(user_name, init_param1, init_param2); + } + #[test] fn test_set_and_get_operator_permissions() { let (contract_address, admin_address, erc20_address) = setup(); @@ -89,6 +104,88 @@ mod permission_tests { assert(operator_permissions_after.value == permission_flags::NONE, 'not NONE'); } + + #[test] + #[should_panic(expected: 'Contract is paused')] + fn test_set_operator_permissions_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let user_name: felt252 = 'Bob'; + let init_param1: felt252 = 'bob@mail.com'; + let init_param2: felt252 = 'bob profile'; + + // Create account + let account_id = dispatcher.create_token_account(user_name, init_param1, init_param2); + + // Create operator address + let operator_address: ContractAddress = contract_address_const::<'operator'>(); + + // Set READ and EXECUTE permissions for the operator + let permissions = Permissions { value: permission_flags::READ | permission_flags::EXECUTE }; + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Grant permissions to the operator + let result = dispatcher.set_operator_permissions(account_id, operator_address, permissions); + } + + + #[test] + #[should_panic(expected: 'Contract is paused')] + fn test_revoke_operator_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let user_name: felt252 = 'Bob'; + let init_param1: felt252 = 'bob@mail.com'; + let init_param2: felt252 = 'bob profile'; + + // Create account + let account_id = dispatcher.create_token_account(user_name, init_param1, init_param2); + + // Create operator address + let operator_address: ContractAddress = contract_address_const::<'operator'>(); + + // Set READ and EXECUTE permissions for the operator + let permissions = Permissions { value: permission_flags::READ | permission_flags::EXECUTE }; + + // Grant permissions to the operator + let result = dispatcher.set_operator_permissions(account_id, operator_address, permissions); + assert(result, 'set perm failed'); + + // Check operator permissions + let operator_permissions = dispatcher.get_permissions(account_id, operator_address); + assert( + operator_permissions.value == (permission_flags::READ | permission_flags::EXECUTE), + 'wrong perm', + ); + + // Verify the operator has specific permissions + let has_read = dispatcher + .has_permission(account_id, operator_address, permission_flags::READ); + let has_execute = dispatcher + .has_permission(account_id, operator_address, permission_flags::EXECUTE); + let has_write = dispatcher + .has_permission(account_id, operator_address, permission_flags::WRITE); + + assert(has_read, 'no READ perm'); + assert(has_execute, 'no EXEC perm'); + assert(!has_write, 'has WRITE perm'); + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Revoke operator permissions + let revoke_result = dispatcher.revoke_operator(account_id, operator_address); + } + + #[test] fn test_manage_operators_permission() { let (contract_address, admin_address, erc20_address) = setup(); @@ -153,6 +250,36 @@ mod permission_tests { assert(!has_write, 'still has WRITE'); } + + #[test] + #[should_panic(expected: 'Contract is paused')] + fn test_modify_account_permissions_should_panic_if_contract_paused() { + let (contract_address, admin_address, erc20_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Create token account + let user_name: felt252 = 'Charlie'; + let init_param1: felt252 = 'charlie@mail.com'; + let init_param2: felt252 = 'charlie profile'; + + let account_id = dispatcher.create_token_account(user_name, init_param1, init_param2); + + // Get initial permissions + let token_account = dispatcher.get_token_bound_account(account_id); + assert(token_account.owner_permissions.value == permission_flags::FULL, 'wrong init perm'); + + // Modify permissions - remove WRITE permission + let modified_permissions = Permissions { + value: permission_flags::FULL & ~permission_flags::WRITE, + }; + + start_cheat_caller_address(contract_address, admin_address); + dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + dispatcher.modify_account_permissions(account_id, modified_permissions); + } + #[test] fn test_multiple_operators() { let (contract_address, admin_address, erc20_address) = setup(); diff --git a/tests/test_subscription.cairo b/tests/test_subscription.cairo index 26d9bd2..dda11be 100644 --- a/tests/test_subscription.cairo +++ b/tests/test_subscription.cairo @@ -320,6 +320,58 @@ fn test_process_recurring_payment() { assert(recurring_result == true, 'Recurring payment failed'); } + +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_process_recurring_payment_should_panic_if_contract_paused() { + // Setup the contract + let (contract_address, admin_address, erc20_address) = setup(); + + // Create dispatchers for both interfaces + let chain_lib_dispatcher = IChainLibDispatcher { contract_address }; + let subscription_dispatcher = IChainLibDispatcher { contract_address }; + + // Create a specific subscriber address and use it consistently + let subscriber_address: ContractAddress = contract_address_const::<'subscriber'>(); + + token_faucet_and_allowance( + chain_lib_dispatcher, subscriber_address, erc20_address, 1000000000000000000, + ); + + // Set the caller to the subscriber for the entire test + cheat_caller_address(contract_address, subscriber_address, CheatSpan::Indefinite); + + // Create a token-bound account + let user_name: felt252 = 'Mark'; + let init_param1: felt252 = 'Mark@yahoo.com'; + let init_param2: felt252 = 'Mark is a boy'; + chain_lib_dispatcher.create_token_account(user_name, init_param1, init_param2); + + // Process an initial payment (caller is already set to subscriber) + let amount: u256 = 100000000000000000; // 0.1 STRK in wei + let result = subscription_dispatcher.process_initial_payment(amount, subscriber_address); + + // Verify the payment was processed successfully + assert(result == true, 'Initial payment failed'); + + // Now process a recurring payment + // Since this is the first subscription, its ID is 0 + let subscription_id: u256 = 0; + + // Advance the block timestamp to simulate time passing (1 day in seconds) + let one_day_in_seconds: u64 = 24 * 60 * 60; + let initial_timestamp = get_block_timestamp(); + let new_timestamp = initial_timestamp + one_day_in_seconds; + snforge_std::cheat_block_timestamp(contract_address, new_timestamp, CheatSpan::Indefinite); + + start_cheat_caller_address(contract_address, admin_address); + subscription_dispatcher.emergency_pause(); + stop_cheat_caller_address(contract_address); + + // Process the recurring payment + subscription_dispatcher.process_recurring_payment(subscription_id); +} + #[test] #[should_panic(expected: 'Insufficient token allowance')] fn test_process_recurring_payment_should_panic_if_insufficient_allowance() { @@ -568,6 +620,49 @@ fn test_verify_payment_admin_only() { assert(false, 'Should have panicked'); } +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_verify_payment_should_panic_if_contract_paused() { + // Setup the contract + let (contract_address, admin_address, erc20_address) = setup(); + + // Create dispatchers for both interfaces + let chain_lib_dispatcher = IChainLibDispatcher { contract_address }; + let subscription_dispatcher = IChainLibDispatcher { contract_address }; + + // Create a specific subscriber address and use it consistently + let subscriber_address: ContractAddress = contract_address_const::<'subscriber'>(); + + // Set the caller to the subscriber for creating a subscription + cheat_caller_address(contract_address, subscriber_address, CheatSpan::Indefinite); + token_faucet_and_allowance( + chain_lib_dispatcher, subscriber_address, erc20_address, 1000000000000000000, + ); + + // Create a token-bound account + let user_name: felt252 = 'Mark'; + let init_param1: felt252 = 'Mark@yahoo.com'; + let init_param2: felt252 = 'Mark is a boy'; + chain_lib_dispatcher.create_token_account(user_name, init_param1, init_param2); + + // Process an initial payment (caller is already set to subscriber) + let amount: u256 = 100000000000000000; // 0.1 STRK in wei + let result = subscription_dispatcher.process_initial_payment(amount, subscriber_address); + + // Verify the payment was processed successfully + assert(result == true, 'Initial payment failed'); + + // The payment ID for the initial payment should be 0 + let payment_id: u256 = 0; + + start_cheat_caller_address(contract_address, admin_address); + subscription_dispatcher.emergency_pause(); + + subscription_dispatcher.verify_payment(payment_id); + + stop_cheat_caller_address(contract_address); +} + // Test that the function panics when payment is not found #[test] #[should_panic(expected: 'Payment not found')] @@ -735,6 +830,48 @@ fn test_process_refund_admin_only() { assert(false, 'Should have panicked'); } +#[test] +#[should_panic(expected: 'Contract is paused')] +fn test_process_refund_should_panic_if_contract_paused() { + // Setup the contract + let (contract_address, admin_address, erc20_address) = setup(); + + // Create dispatchers for both interfaces + let chain_lib_dispatcher = IChainLibDispatcher { contract_address }; + let subscription_dispatcher = IChainLibDispatcher { contract_address }; + + // Create a specific subscriber address and use it consistently + let subscriber_address: ContractAddress = contract_address_const::<'subscriber'>(); + + token_faucet_and_allowance( + chain_lib_dispatcher, subscriber_address, erc20_address, 1000000000000000000, + ); + + // Set the caller to the subscriber for creating a subscription + cheat_caller_address(contract_address, subscriber_address, CheatSpan::Indefinite); + + // Create a token-bound account + let user_name: felt252 = 'Mark'; + let init_param1: felt252 = 'Mark@yahoo.com'; + let init_param2: felt252 = 'Mark is a boy'; + chain_lib_dispatcher.create_token_account(user_name, init_param1, init_param2); + + // Process an initial payment (caller is already set to subscriber) + let amount: u256 = 100000000000000000; // 0.1 STRK in wei + let result = subscription_dispatcher.process_initial_payment(amount, subscriber_address); + + // Verify the payment was processed successfully + assert(result == true, 'Initial payment failed'); + + // Since this is the first subscription, its ID is 0 + let subscription_id: u256 = 0; + + start_cheat_caller_address(contract_address, admin_address); + subscription_dispatcher.emergency_pause(); + + subscription_dispatcher.process_refund(subscription_id); + stop_cheat_caller_address(contract_address); +} // Test that the function panics when subscription is not found #[test] #[should_panic(expected: 'Subscription not found')]