Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/base/types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,22 @@ pub mod delegation_flags {
// Combined flag for full delegation capabilities
pub const FULL_DELEGATION: u64 = 0xF0000;
}
#[derive(Copy, Drop, Serde, starknet::Store, Clone, PartialEq, Debug)]
pub enum PurchaseStatus {
#[default]
Pending,
Completed,
Failed,
Refunded,
}

#[derive(Drop, Serde, starknet::Store, Debug)]
pub struct Purchase {
pub id: u256,
pub content_id: felt252,
pub buyer: ContractAddress,
pub price: u256,
pub status: PurchaseStatus,
pub timestamp: u64,
pub transaction_hash: felt252,
}
210 changes: 208 additions & 2 deletions src/chainlib/ChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod ChainLib {
use core::array::Array;
use core::array::ArrayTrait;
use core::option::OptionTrait;

use core::traits::Into;
use starknet::storage::{
Map, MutableVecTrait, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
StoragePointerWriteAccess, Vec, VecTrait,
Expand All @@ -17,7 +17,7 @@ pub mod ChainLib {

use crate::base::types::{
TokenBoundAccount, User, Role, Rank, Permissions, permission_flags, AccessRule, AccessType,
VerificationRequirement, VerificationType,
VerificationRequirement, VerificationType, Purchase, PurchaseStatus,
};

// Define delegation-specific structures and constants
Expand Down Expand Up @@ -155,13 +155,26 @@ pub mod ChainLib {
user_reputation_verifications: Map<ContractAddress, bool>,
user_ownership_verifications: Map<ContractAddress, bool>,
user_custom_verifications: Map<ContractAddress, bool>,
content_prices: Map::<felt252, u256>, // Maps content_id to price
next_purchase_id: u256, // Tracking the next available purchase ID
purchases: Map::<u256, Purchase>, // Store purchases by ID
user_purchase_count: Map::<ContractAddress, u32>, // Count of purchases per user
user_purchase_ids: Map::<
(ContractAddress, u32), u256,
>, // Map of (user, index) to purchase ID
content_purchase_count: Map::<felt252, u32>, // Count of purchases per content
content_purchase_ids: Map::<
(felt252, u32), u256,
> // Map of (content_id, index) to purchase ID
}


#[constructor]
fn constructor(ref self: ContractState, admin: ContractAddress) {
// Store the values in contract state
self.admin.write(admin);
// Initialize purchase ID counter
self.next_purchase_id.write(1_u256);
}

#[event]
Expand All @@ -187,6 +200,8 @@ pub mod ChainLib {
DelegationRevoked: DelegationRevoked,
DelegationUsed: DelegationUsed,
DelegationExpired: DelegationExpired,
ContentPurchased: ContentPurchased,
PurchaseStatusUpdated: PurchaseStatusUpdated,
}

#[derive(Drop, starknet::Event)]
Expand Down Expand Up @@ -321,6 +336,22 @@ pub mod ChainLib {
pub timestamp: u64,
}

#[derive(Drop, starknet::Event)]
pub struct ContentPurchased {
pub purchase_id: u256,
pub content_id: felt252,
pub buyer: ContractAddress,
pub price: u256,
pub timestamp: u64,
}

#[derive(Drop, starknet::Event)]
pub struct PurchaseStatusUpdated {
pub purchase_id: u256,
pub new_status: u8, // Using u8 for status code instead of PurchaseStatus enum
pub timestamp: u64,
}

#[abi(embed_v0)]
impl ChainLibNetImpl of IChainLib<ContractState> {
fn create_token_account(
Expand Down Expand Up @@ -387,6 +418,7 @@ pub mod ChainLib {
token_bound_account
}


fn register_user(
ref self: ContractState, username: felt252, role: Role, rank: Rank, metadata: felt252,
) -> u256 {
Expand Down Expand Up @@ -1506,5 +1538,179 @@ pub mod ChainLib {

true
}

/// @notice Sets the price for a content item (admin only)
/// @dev This function allows the admin to set or update the price of a content item.
/// @param self The contract state reference.
/// @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) {
// Only admin can set content prices
let caller = get_caller_address();
assert(self.admin.read() == caller, 'Admin only');

// Set the price for the content
self.content_prices.write(content_id, price);
}

/// @notice Initiates a purchase for a specific content.
/// @dev Creates a purchase record with a pending status and emits an event.
/// @param self The contract state reference.
/// @param content_id The unique identifier of the content being purchased.
/// @param transaction_hash The hash of the transaction being used for payment.
/// @return The unique ID of the newly created purchase.
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");

// Get the buyer's address
let buyer = get_caller_address();

// 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(),
transaction_hash: transaction_hash,
};

// 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
}

/// @notice Retrieves details of a specific purchase.
/// @dev Fetches purchase information by its ID.
/// @param self The contract state reference.
/// @param purchase_id The unique identifier of the purchase.
/// @return Purchase The purchase details.
fn get_purchase_details(ref self: ContractState, purchase_id: u256) -> Purchase {
// Fetch and return the purchase details from storage
let purchase = self.purchases.read(purchase_id);
purchase
}

/// @notice Retrieves all purchases made by a specific user.
/// @dev Returns an array of purchase records for the user.
/// @param self The contract state reference.
/// @param user_address The address of the user whose purchases are being retrieved.
/// @return Array<Purchase> An array of purchase records for the user.
fn get_user_purchases(
ref self: ContractState, user_address: ContractAddress,
) -> Array<Purchase> {
// Initialize an empty array to hold the purchases
let mut purchases: Array<Purchase> = ArrayTrait::new();

// Get the number of purchases for this user
let purchase_count = self.user_purchase_count.read(user_address);

// Iterate through the purchase IDs and fetch each purchase
let mut i: u32 = 0;

while i < purchase_count {
// Get the purchase ID at the current index
let purchase_id = self.user_purchase_ids.read((user_address, i));

// Fetch the purchase details using the ID
let purchase = self.purchases.read(purchase_id);

// Add the purchase to the array
purchases.append(purchase);

// Move to the next index
i += 1;
};

// Return the array of purchases
purchases
}

/// @notice Verifies if a purchase is valid and completed.
/// @dev Checks the status of a purchase to confirm it has been completed successfully.
/// @param self The contract state reference.
/// @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 {
// Get the purchase details
let purchase = self.purchases.read(purchase_id);

// A purchase is valid if its status is Completed
if purchase.status == PurchaseStatus::Completed {
return true;
} else {
return false;
}
}

/// @notice Updates the status of a purchase.
/// @dev Only admin can update the status to prevent unauthorized changes.
/// @param self The contract state reference.
/// @param purchase_id The unique identifier of the purchase.
/// @param status The new status to set for the purchase.
/// @return bool True if the status was updated successfully.
fn update_purchase_status(
ref self: ContractState, purchase_id: u256, status: PurchaseStatus,
) -> bool {
// Only admin can update purchase status
let caller = get_caller_address();
assert(self.admin.read() == caller, 'Only admin can update status');

// Get the current purchase
let mut purchase = self.purchases.read(purchase_id);

// Validate that we're not trying to update a purchase that doesn't exist
assert(purchase.id == purchase_id, 'Purchase does not exist');

// Update the status
purchase.status = status;

// Save the updated purchase
self.purchases.write(purchase_id, purchase);

// Emit event for the status update
let timestamp = get_block_timestamp();

// Convert PurchaseStatus to u8
let status_code: u8 = match status {
PurchaseStatus::Pending => 0_u8,
PurchaseStatus::Completed => 1_u8,
PurchaseStatus::Failed => 2_u8,
PurchaseStatus::Refunded => 3_u8,
};

self.emit(PurchaseStatusUpdated { purchase_id, new_status: status_code, timestamp });

true
}
}
}
17 changes: 16 additions & 1 deletion src/interfaces/IChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use starknet::ContractAddress;
use core::array::Array;
use crate::base::types::{
TokenBoundAccount, User, Role, Rank, Permissions, AccessRule, VerificationRequirement,
VerificationType,
VerificationType, Purchase, PurchaseStatus,
};
use crate::chainlib::ChainLib::ChainLib::{
Category, Subscription, Payment, ContentType, ContentMetadata, DelegationInfo,
Expand Down Expand Up @@ -63,6 +63,9 @@ pub trait IChainLib<TContractState> {
) -> felt252;
fn get_content(ref self: TContractState, content_id: felt252) -> ContentMetadata;


fn set_content_price(ref self: TContractState, content_id: felt252, price: u256);

// Payment System
fn process_initial_payment(
ref self: TContractState, amount: u256, subscriber: ContractAddress,
Expand Down Expand Up @@ -179,4 +182,16 @@ pub trait IChainLib<TContractState> {
fn initialize_access_control(ref self: TContractState, default_cache_ttl: u64) -> bool;

fn clear_access_cache(ref self: TContractState, user_id: u256, content_id: felt252) -> bool;

fn purchase_content(
ref self: TContractState, content_id: felt252, transaction_hash: felt252,
) -> u256;
fn get_purchase_details(ref self: TContractState, purchase_id: u256) -> Purchase;
fn get_user_purchases(
ref self: TContractState, user_address: ContractAddress,
) -> Array<Purchase>;
fn verify_purchase(ref self: TContractState, purchase_id: u256) -> bool;
fn update_purchase_status(
ref self: TContractState, purchase_id: u256, status: PurchaseStatus,
) -> bool;
}
Loading
Loading