diff --git a/src/base/types.cairo b/src/base/types.cairo index b8da1c9..9abd5ca 100644 --- a/src/base/types.cairo +++ b/src/base/types.cairo @@ -145,4 +145,5 @@ pub struct Purchase { pub status: PurchaseStatus, pub timestamp: u64, pub transaction_hash: felt252, + pub timeout_expiry: u64, } diff --git a/src/chainlib/ChainLib.cairo b/src/chainlib/ChainLib.cairo index 7feeb43..7e9d65a 100644 --- a/src/chainlib/ChainLib.cairo +++ b/src/chainlib/ChainLib.cairo @@ -1,24 +1,20 @@ #[starknet::contract] pub mod ChainLib { - use core::array::Array; - use core::array::ArrayTrait; + use core::array::{Array, ArrayTrait}; use core::option::OptionTrait; use core::traits::Into; use starknet::storage::{ Map, MutableVecTrait, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, Vec, VecTrait, }; - - use starknet::{ - ContractAddress, get_block_timestamp, get_caller_address, contract_address_const, + ContractAddress, contract_address_const, get_block_timestamp, get_caller_address, }; - use crate::interfaces::IChainLib::IChainLib; - use crate::base::types::{ - TokenBoundAccount, User, Role, Rank, Permissions, permission_flags, AccessRule, AccessType, - Status, VerificationRequirement, VerificationType, Purchase, PurchaseStatus, + AccessRule, AccessType, Permissions, Purchase, PurchaseStatus, Rank, Role, Status, + TokenBoundAccount, User, VerificationRequirement, VerificationType, permission_flags, }; + use crate::interfaces::IChainLib::IChainLib; // Define delegation-specific structures and constants @@ -112,60 +108,88 @@ pub mod ChainLib { #[storage] struct Storage { - admin: ContractAddress, - current_account_id: u256, - accounts: Map, - accountsaddr: Map, - next_course_id: u256, - user_id: u256, - users: Map, - creators_content: Map::, - content: Map::, - content_tags: Map::>, - // Subscription related storage - subscription_id: u256, - subscriptions: Map::, - // Instead of storing arrays directly, we'll use a counter-based approach - user_subscription_count: Map::, - user_subscription_by_index: Map::<(ContractAddress, u256), u256>, - payment_id: u256, - payments: Map::, - // Similar counter-based approach for subscription payments - subscription_payment_count: Map::, - subscription_payment_by_index: Map::<(u256, u256), u256>, - next_content_id: felt252, - user_by_address: Map, - operator_permissions: Map::<(u256, ContractAddress), Permissions>, - content_access: Map::, - premium_content_access: Map::<(u256, felt252), bool>, - access_cache: Map::<(u256, felt252), AccessCache>, - access_blacklist: Map::<(u256, felt252), bool>, - cache_ttl: u64, - delegations: Map::<(ContractAddress, u64), DelegationInfo>, - delegation_nonces: Map::, - delegation_history: Map::<(ContractAddress, ContractAddress), u64>, - content_access_rules_count: Map, - content_access_rules: Map<(felt252, u32), AccessRule>, - user_content_permissions: Map<(ContractAddress, felt252), Permissions>, - content_verification_requirements_count: Map, - content_verification_requirements: Map<(felt252, u32), VerificationRequirement>, - user_verifications: Map<(ContractAddress, VerificationType), bool>, - user_identity_verifications: Map, - user_payment_verifications: Map, - user_reputation_verifications: Map, - user_ownership_verifications: Map, - user_custom_verifications: Map, - content_prices: Map::, // Maps content_id to price - next_purchase_id: u256, // Tracking the next available purchase ID - purchases: Map::, // Store purchases by ID - user_purchase_count: Map::, // Count of purchases per user - user_purchase_ids: Map::< - (ContractAddress, u32), u256, - >, // Map of (user, index) to purchase ID - content_purchase_count: Map::, // Count of purchases per content - content_purchase_ids: Map::< - (felt252, u32), u256, - > // Map of (content_id, index) to purchase ID + admin: ContractAddress, // Address of the contract admin + current_account_id: u256, // Counter for token-bound account IDs + accounts: Map, // Maps account ID to TokenBoundAccount + accountsaddr: Map, // Maps address to TokenBoundAccount + next_course_id: u256, // Counter for course IDs (unused?) + user_id: u256, // Counter for user IDs + users: Map, // Maps user ID to User + creators_content: Map< + ContractAddress, ContentMetadata, + >, // Maps creator address to their content + content: Map, // Maps content ID to ContentMetadata + content_tags: Map>, // Maps content to associated tags + subscription_id: u256, // Counter for subscription IDs + subscriptions: Map, // Maps subscription ID to Subscription + user_subscription_count: Map, // Count of subscriptions per user + user_subscription_by_index: Map< + (ContractAddress, u256), u256, + >, // Maps (user, index) to subscription ID + payment_id: u256, // Counter for payment IDs + payments: Map, // Maps payment ID to Payment + subscription_payment_count: Map, // Count of payments per subscription + subscription_payment_by_index: Map< + (u256, u256), u256, + >, // Maps (subscription_id, index) to payment ID + next_content_id: felt252, // Counter for content IDs + user_by_address: Map, // Maps user address to User + operator_permissions: Map< + (u256, ContractAddress), Permissions, + >, // Maps (account_id, operator) to Permissions + content_access: Map, // Maps content ID to access configuration + premium_content_access: Map< + (u256, felt252), bool, + >, // Maps (user_id, content_id) to premium access status + access_cache: Map< + (u256, felt252), AccessCache, + >, // Maps (user_id, content_id) to cached access status + access_blacklist: Map< + (u256, felt252), bool, + >, // Maps (user_id, content_id) to blacklist status + cache_ttl: u64, // Cache time-to-live in seconds + delegations: Map< + (ContractAddress, u64), DelegationInfo, + >, // Maps (delegator, permission) to DelegationInfo + delegation_nonces: Map, // Nonce for tracking delegations + delegation_history: Map< + (ContractAddress, ContractAddress), u64, + >, // Tracks delegation history + content_access_rules_count: Map, // Count of access rules per content + content_access_rules: Map< + (felt252, u32), AccessRule, + >, // Maps (content_id, index) to AccessRule + user_content_permissions: Map< + (ContractAddress, felt252), Permissions, + >, // Maps (user, content_id) to Permissions + content_verification_requirements_count: Map< + felt252, u32, + >, // Count of verification requirements per content + content_verification_requirements: Map< + (felt252, u32), VerificationRequirement, + >, // Maps (content_id, index) to VerificationRequirement + user_verifications: Map< + (ContractAddress, VerificationType), bool, + >, // Maps (user, verification_type) to verification status + user_identity_verifications: Map< + ContractAddress, bool, + >, // Identity verification status for users + user_payment_verifications: Map< + ContractAddress, bool, + >, // Payment verification status for users + user_reputation_verifications: Map< + ContractAddress, bool, + >, // Reputation verification status for users + user_ownership_verifications: Map< + ContractAddress, bool, + >, // Ownership verification status for users + user_custom_verifications: Map< + ContractAddress, bool, + >, // Custom verification status for users + content_prices: Map, // Maps content_id to price + next_purchase_id: u256, // Counter for purchase IDs + purchases: Map, // Maps purchase ID to Purchase + purchase_timeout_duration: u64, } @@ -175,6 +199,7 @@ pub mod ChainLib { self.admin.write(admin); // Initialize purchase ID counter self.next_purchase_id.write(1_u256); + self.purchase_timeout_duration.write(3600); } #[event] @@ -1068,7 +1093,7 @@ pub mod ChainLib { if req.valid_until != 0 && req.valid_until < current_time { i += 1; continue; - }; + } // Check verification status based on type let is_verified = match req.requirement_type { @@ -1082,7 +1107,7 @@ pub mod ChainLib { if !is_verified { status = false; break; - }; + } i += 1; }; status @@ -1114,7 +1139,7 @@ pub mod ChainLib { VerificationType::Custom => { self.user_custom_verifications.write(user, is_verified); }, - }; + } self .emit( @@ -1619,50 +1644,34 @@ pub mod ChainLib { fn purchase_content( ref self: ContractState, content_id: felt252, transaction_hash: felt252, ) -> u256 { - // Validate input parameters assert!(content_id != 0, "Content ID cannot be empty"); assert!(transaction_hash != 0, "Transaction hash cannot be empty"); - // Get the price for the content let price = self.content_prices.read(content_id); - assert!(price > 0, "Content either doesn't exist"); + assert!(price > 0, "Content either doesn't exist or has no price"); - // Get the buyer's address let buyer = get_caller_address(); + let current_time = get_block_timestamp(); - // Get the next purchase ID and increment for future use let purchase_id = self.next_purchase_id.read(); self.next_purchase_id.write(purchase_id + 1); - // Create the purchase record let purchase = Purchase { id: purchase_id, content_id: content_id, buyer: buyer, price: price, status: PurchaseStatus::Pending, - timestamp: get_block_timestamp(), + timestamp: current_time, transaction_hash: transaction_hash, + timeout_expiry: current_time + self.purchase_timeout_duration.read(), }; - // Store the purchase in the purchases mapping self.purchases.write(purchase_id, purchase); - // Add the purchase ID to the user's purchase list - let user_purchase_count = self.user_purchase_count.read(buyer); - self.user_purchase_ids.write((buyer, user_purchase_count), purchase_id); - self.user_purchase_count.write(buyer, user_purchase_count + 1); - - // Add the purchase ID to the content's purchase list - let content_purchase_count = self.content_purchase_count.read(content_id); - self.content_purchase_ids.write((content_id, content_purchase_count), purchase_id); - self.content_purchase_count.write(content_id, content_purchase_count + 1); - - // Emit event for the purchase let timestamp = get_block_timestamp(); self.emit(ContentPurchased { purchase_id, content_id, buyer, price, timestamp }); - // Return the purchase ID purchase_id } @@ -1688,23 +1697,40 @@ pub mod ChainLib { // Initialize an empty array to hold the purchases let mut purchases: Array = ArrayTrait::new(); - // Get the number of purchases for this user - let purchase_count = self.user_purchase_count.read(user_address); + // Iterate through all purchase IDs up to next_purchase_id + let total_purchases = self.next_purchase_id.read(); + let mut i: u256 = 1; // Start from 1 as purchase IDs begin at 1 - // Iterate through the purchase IDs and fetch each purchase - let mut i: u32 = 0; + while i < total_purchases { + let purchase = self.purchases.read(i); + if purchase.buyer == user_address { + purchases.append(purchase); + } + i += 1; + }; - while i < purchase_count { - // Get the purchase ID at the current index - let purchase_id = self.user_purchase_ids.read((user_address, i)); + // Return the array of purchases + purchases + } - // Fetch the purchase details using the ID - let purchase = self.purchases.read(purchase_id); + /// @notice Retrieves all purchases for a specific content item. + /// @dev Iterates through the purchases mapping to find purchases for the given content_id. + /// @param self The contract state reference. + /// @param content_id The unique identifier of the content. + /// @return Array An array of purchase records for the content. + fn get_content_purchases(ref self: ContractState, content_id: felt252) -> Array { + // Initialize an empty array to hold the purchases + let mut purchases: Array = ArrayTrait::new(); - // Add the purchase to the array - purchases.append(purchase); + // Iterate through all purchase IDs up to next_purchase_id + let total_purchases = self.next_purchase_id.read(); + let mut i: u256 = 1; // Start from 1 as purchase IDs begin at 1 - // Move to the next index + while i < total_purchases { + let purchase = self.purchases.read(i); + if purchase.content_id == content_id { + purchases.append(purchase); + } i += 1; }; diff --git a/src/interfaces/IChainLib.cairo b/src/interfaces/IChainLib.cairo index cdfd6d8..f294c7f 100644 --- a/src/interfaces/IChainLib.cairo +++ b/src/interfaces/IChainLib.cairo @@ -1,11 +1,11 @@ -use starknet::ContractAddress; use core::array::Array; +use starknet::ContractAddress; use crate::base::types::{ - TokenBoundAccount, User, Role, Rank, Permissions, AccessRule, VerificationRequirement, - VerificationType, Purchase, PurchaseStatus, + AccessRule, Permissions, Purchase, PurchaseStatus, Rank, Role, TokenBoundAccount, User, + VerificationRequirement, VerificationType, }; use crate::chainlib::ChainLib::ChainLib::{ - Category, Subscription, Payment, ContentType, ContentMetadata, DelegationInfo, + Category, ContentMetadata, ContentType, DelegationInfo, Payment, Subscription, }; #[starknet::interface] @@ -206,4 +206,5 @@ pub trait IChainLib { fn update_purchase_status( ref self: TContractState, purchase_id: u256, status: PurchaseStatus, ) -> bool; + fn get_content_purchases(ref self: TContractState, content_id: felt252) -> Array; } diff --git a/tests/lib.cairo b/tests/lib.cairo index 31c6829..6da5df4 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -1,8 +1,8 @@ #[cfg(test)] pub mod test_ChainLib; -pub mod test_subscription; -pub mod test_permissions; -pub mod test_contentpost; -pub mod test_contentaccess; pub mod test_account_delegation; +pub mod test_contentaccess; +pub mod test_contentpost; +pub mod test_permissions; +pub mod test_subscription; diff --git a/tests/test_ChainLib.cairo b/tests/test_ChainLib.cairo index 65d4a38..5463b5f 100644 --- a/tests/test_ChainLib.cairo +++ b/tests/test_ChainLib.cairo @@ -1,17 +1,16 @@ // Import the contract modules +use chain_lib::base::types::{PurchaseStatus, Rank, Role, Status}; use chain_lib::chainlib::ChainLib; - +use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentType}; use chain_lib::interfaces::IChainLib::{IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait}; use snforge_std::{ - CheatSpan, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address, - cheat_caller_address, declare, stop_cheat_caller_address, + CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, + start_cheat_caller_address, stop_cheat_caller_address, }; -use starknet::{ContractAddress}; +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 chain_lib::base::types::{Role, Rank, PurchaseStatus, Status}; -use chain_lib::chainlib::ChainLib::ChainLib::{ContentType, Category}; /// Helper function to create a content item with a price /// We'll use the set_content_price function implemented in the contract diff --git a/tests/test_account_delegation.cairo b/tests/test_account_delegation.cairo index f052b04..54c23e8 100644 --- a/tests/test_account_delegation.cairo +++ b/tests/test_account_delegation.cairo @@ -6,29 +6,26 @@ const PERMISSION_SIGN: u64 = 0x2; const PERMISSION_CALL: u64 = 0x4; const PERMISSION_ADMIN: u64 = 0x8; use chain_lib::chainlib::ChainLib::ChainLib::{ - DelegationInfo, delegation_flags, Event, DelegationCreated, DelegationExpired, DelegationUsed, - DelegationRevoked, + DelegationCreated, DelegationExpired, DelegationInfo, DelegationRevoked, DelegationUsed, Event, + delegation_flags, }; +use chain_lib::interfaces::IChainLib::{IChainLibDispatcher, IChainLibDispatcherTrait}; // use chain_lib::interfaces::IChainLib::{ // IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait // }; use core::array::ArrayTrait; use core::result::ResultTrait; -use chain_lib::interfaces::IChainLib::{IChainLibDispatcher, IChainLibDispatcherTrait}; - -use starknet::class_hash::ClassHash; -use starknet::contract_address::contract_address_const; -use starknet::get_caller_address; // use chain_lib::base::types::{Permissions, permission_flags, DelegationInfo, delegation_flags}; // use chain_lib::base::types::{DelegationInfo, delegation_flags}; use snforge_std::{ ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events, - start_cheat_caller_address, stop_cheat_caller_address, start_cheat_block_timestamp, - stop_cheat_block_timestamp, + start_cheat_block_timestamp, start_cheat_caller_address, stop_cheat_block_timestamp, + stop_cheat_caller_address, }; - -use starknet::{ContractAddress, get_block_timestamp}; +use starknet::class_hash::ClassHash; +use starknet::contract_address::contract_address_const; +use starknet::{ContractAddress, get_block_timestamp, get_caller_address}; fn setup() -> ContractAddress { let contract_class = declare("ChainLib").unwrap().contract_class(); diff --git a/tests/test_contentaccess.cairo b/tests/test_contentaccess.cairo index 118673d..89297f6 100644 --- a/tests/test_contentaccess.cairo +++ b/tests/test_contentaccess.cairo @@ -1,23 +1,22 @@ #[cfg(test)] mod permission_tests { + use chain_lib::base::types::{ + AccessRule, AccessType, Permissions, VerificationRequirement, VerificationType, + permission_flags, + }; use chain_lib::chainlib::ChainLib; + use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentMetadata, ContentType}; use chain_lib::interfaces::IChainLib::{ IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait, }; + use core::array::ArrayTrait; + use core::option::OptionTrait; use snforge_std::{ CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, }; - use starknet::ContractAddress; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; - use starknet::get_caller_address; - use core::array::ArrayTrait; - use core::option::OptionTrait; - use chain_lib::base::types::{ - permission_flags, AccessRule, AccessType, VerificationRequirement, VerificationType, - Permissions, - }; - use chain_lib::chainlib::ChainLib::ChainLib::{ContentType, Category, ContentMetadata}; + use starknet::{ContractAddress, get_caller_address}; fn setup() -> (ContractAddress, ContractAddress) { let declare_result = declare("ChainLib"); diff --git a/tests/test_contentpost.cairo b/tests/test_contentpost.cairo index 0a91ef4..4ef3e75 100644 --- a/tests/test_contentpost.cairo +++ b/tests/test_contentpost.cairo @@ -1,16 +1,15 @@ +use chain_lib::base::types::{Rank, Role}; use chain_lib::chainlib::ChainLib; - +use chain_lib::chainlib::ChainLib::ChainLib::{Category, ContentMetadata, ContentType}; use chain_lib::interfaces::IChainLib::{IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait}; use snforge_std::{ - CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, spy_events, - EventSpy, EventSpyAssertionsTrait, + CheatSpan, ContractClassTrait, DeclareResultTrait, EventSpy, EventSpyAssertionsTrait, + cheat_caller_address, declare, spy_events, }; 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 chain_lib::base::types::{Role, Rank}; -use chain_lib::chainlib::ChainLib::ChainLib::{ContentType, Category, ContentMetadata}; fn setup() -> (ContractAddress, ContractAddress) { diff --git a/tests/test_permissions.cairo b/tests/test_permissions.cairo index 4f6c2a9..b431746 100644 --- a/tests/test_permissions.cairo +++ b/tests/test_permissions.cairo @@ -1,5 +1,6 @@ #[cfg(test)] mod permission_tests { + use chain_lib::base::types::{Permissions, permission_flags}; use chain_lib::chainlib::ChainLib; use chain_lib::interfaces::IChainLib::{ IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait, @@ -7,11 +8,9 @@ mod permission_tests { use snforge_std::{ CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, }; - use starknet::ContractAddress; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; - use starknet::get_caller_address; - use chain_lib::base::types::{Permissions, permission_flags}; + use starknet::{ContractAddress, get_caller_address}; fn setup() -> (ContractAddress, ContractAddress) { let declare_result = declare("ChainLib"); diff --git a/tests/test_subscription.cairo b/tests/test_subscription.cairo index 160fdd5..afc6325 100644 --- a/tests/test_subscription.cairo +++ b/tests/test_subscription.cairo @@ -1,17 +1,16 @@ +use chain_lib::base::types::{Rank, Role}; +use chain_lib::chainlib::ChainLib::ChainLib::{ + Event, PaymentProcessed, PaymentVerified, RecurringPaymentProcessed, RefundProcessed, +}; use chain_lib::interfaces::IChainLib::{IChainLib, IChainLibDispatcher, IChainLibDispatcherTrait}; - use snforge_std::{ - CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, spy_events, - EventSpyAssertionsTrait, + CheatSpan, ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, + cheat_caller_address, declare, spy_events, }; -use starknet::{ContractAddress, get_block_timestamp}; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; use starknet::testing::{set_caller_address, set_contract_address}; -use chain_lib::base::types::{Role, Rank}; -use chain_lib::chainlib::ChainLib::ChainLib::{ - Event, PaymentProcessed, RecurringPaymentProcessed, PaymentVerified, RefundProcessed, -}; +use starknet::{ContractAddress, get_block_timestamp}; fn setup() -> (ContractAddress, ContractAddress) {