Skip to content
Open
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
80 changes: 71 additions & 9 deletions StarShopContracts/product-voting-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ pub mod types;
pub mod vote;
use limits::VoteLimiter;
use ranking::RankingCalculator;
use types::{Error, VoteType};
use types::{AdminConfig, Error, VoteType};
use vote::VoteManager;

pub trait ProductVotingTrait {
// Core Functions
fn init(env: Env);
fn create_product(env: Env, id: Symbol, name: Symbol) -> Result<(), Error>;
fn create_product(env: Env, id: Symbol, name: Symbol, creator: Address) -> Result<(), Error>;
fn cast_vote(
env: Env,
product_id: Symbol,
Expand All @@ -22,6 +23,24 @@ pub trait ProductVotingTrait {
) -> Result<(), Error>;
fn get_product_score(env: Env, product_id: Symbol) -> i32;
fn get_trending_products(env: Env) -> Vec<Symbol>;

// SECURITY FIX: Admin Functions
fn init_admin(
env: Env,
admin: Address,
max_products_per_user: u32,
voting_period_days: u32,
reversal_window_hours: u32,
) -> Result<(), Error>;
fn get_admin_config(env: Env) -> Result<AdminConfig, Error>;
fn deactivate_product(env: Env, product_id: Symbol) -> Result<(), Error>;
fn reset_rankings(env: Env) -> Result<(), Error>;

// ANALYTICS: Statistics and monitoring
fn get_ranking_stats(env: Env) -> Option<(u32, i32, i32)>;

// TRANSPARENCY: Vote history access
fn get_vote_history(env: Env, product_id: Symbol) -> Option<Vec<types::VoteHistoryEntry>>;
}

#[contract]
Expand All @@ -35,8 +54,9 @@ impl ProductVotingTrait for ProductVoting {
VoteLimiter::init(&env);
}

fn create_product(env: Env, id: Symbol, name: Symbol) -> Result<(), Error> {
VoteManager::create_product(&env, id, name)
// SECURITY FIX: Enhanced product creation with authorization
fn create_product(env: Env, id: Symbol, name: Symbol, creator: Address) -> Result<(), Error> {
VoteManager::create_product(&env, id, name, creator)
}
Comment thread
raizo07 marked this conversation as resolved.

fn cast_vote(
Expand All @@ -45,13 +65,16 @@ impl ProductVotingTrait for ProductVoting {
vote_type: VoteType,
voter: Address,
) -> Result<(), Error> {
// Check vote limits first
// SECURITY FIX: Check limits without recording the vote first
VoteLimiter::check_limits(&env, &voter)?;

// Cast the vote - clone product_id since we'll use it again
VoteManager::cast_vote(&env, product_id.clone(), vote_type, voter)?;
// SECURITY FIX: Cast the vote and only proceed if successful
VoteManager::cast_vote(&env, product_id.clone(), vote_type, voter.clone())?;

// Update rankings
// SECURITY FIX: Only record the vote count AFTER successful casting
VoteLimiter::record_vote(&env, &voter)?;

// Update rankings after successful vote
RankingCalculator::update_ranking(&env, product_id);

Ok(())
Expand All @@ -64,4 +87,43 @@ impl ProductVotingTrait for ProductVoting {
fn get_trending_products(env: Env) -> Vec<Symbol> {
RankingCalculator::get_trending(&env)
}
}

// ADMIN FUNCTIONS: Require admin authorization
fn init_admin(
env: Env,
admin: Address,
max_products_per_user: u32,
voting_period_days: u32,
reversal_window_hours: u32,
) -> Result<(), Error> {
VoteManager::init_admin(
&env,
admin,
max_products_per_user,
voting_period_days,
reversal_window_hours,
)
}

fn get_admin_config(env: Env) -> Result<AdminConfig, Error> {
VoteManager::get_admin_config(&env)
}

fn deactivate_product(env: Env, product_id: Symbol) -> Result<(), Error> {
VoteManager::deactivate_product(&env, product_id)
}

fn reset_rankings(env: Env) -> Result<(), Error> {
RankingCalculator::reset_rankings(&env)
}

// ANALYTICS: Performance monitoring
fn get_ranking_stats(env: Env) -> Option<(u32, i32, i32)> {
RankingCalculator::get_ranking_stats(&env)
}

// TRANSPARENCY: Audit trail access
fn get_vote_history(env: Env, product_id: Symbol) -> Option<Vec<types::VoteHistoryEntry>> {
VoteManager::get_product(&env, product_id).map(|product| product.vote_history)
}
}
77 changes: 54 additions & 23 deletions StarShopContracts/product-voting-contract/src/limits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::types::Error;
use soroban_sdk::{symbol_short, Address, Env, Map, Vec};
use crate::types::{DataKey, Error};
use soroban_sdk::{Address, Env, Map, Vec};

pub struct VoteLimiter;

Expand All @@ -11,55 +11,86 @@ impl VoteLimiter {
let usr_votes: Map<Address, Vec<u64>> = Map::new(env);
env.storage()
.instance()
.set(&symbol_short!("usr_votes"), &usr_votes);
.set(&DataKey::UserVotes, &usr_votes);
}

pub fn check_limits(env: &Env, voter: &Address) -> Result<(), Error> {
let mut usr_votes: Map<Address, Vec<u64>> = env
let usr_votes: Map<Address, Vec<u64>> = env
.storage()
.instance()
.get(&symbol_short!("usr_votes"))
.get(&DataKey::UserVotes)
.unwrap_or_else(|| Map::new(env));

let mut user_recent_votes = usr_votes.get(voter.clone()).unwrap_or(Vec::new(env));
let user_recent_votes = usr_votes.get(voter.clone()).unwrap_or(Vec::new(env));

let now = env.ledger().timestamp();

// Check account age
// Check account age - FIXED: Now properly validates or rejects unknown accounts
if let Some(created_at) = Self::get_account_creation_time(env, voter) {
if now - created_at < MIN_ACCOUNT_AGE {
if now < created_at + MIN_ACCOUNT_AGE {
return Err(Error::AccountTooNew);
}
} else {
// SECURITY FIX: Reject accounts where we cannot verify age
return Err(Error::AccountTooNew);
}

// Remove votes older than 24 hours using manual filtering
let day_ago = now - 24 * 60 * 60;
let mut filtered_votes = Vec::new(env);
let day_ago = if now >= 24 * 60 * 60 { now - 24 * 60 * 60 } else { 0 };
let mut recent_count = 0u32;
for i in 0..user_recent_votes.len() {
let timestamp = user_recent_votes.get(i).unwrap();
if timestamp > day_ago {
filtered_votes.push_back(timestamp);
if let Some(timestamp) = user_recent_votes.get(i) {
if timestamp > day_ago {
recent_count = recent_count.saturating_add(1);
if recent_count >= DAILY_VOTE_LIMIT {
return Err(Error::DailyLimitReached);
}
}
}
}
user_recent_votes = filtered_votes;

// Check daily limit
if user_recent_votes.len() >= DAILY_VOTE_LIMIT {
return Err(Error::DailyLimitReached);
Ok(())
}

// SECURITY FIX: Record vote only after successful validation
pub fn record_vote(env: &Env, voter: &Address) -> Result<(), Error> {
let mut usr_votes: Map<Address, Vec<u64>> = env
.storage()
.instance()
.get(&DataKey::UserVotes)
.unwrap_or_else(|| Map::new(env));

let user_recent_votes = usr_votes.get(voter.clone()).unwrap_or(Vec::new(env));
let now = env.ledger().timestamp();

// Remove votes older than 24 hours
let day_ago = if now >= 24 * 60 * 60 { now - 24 * 60 * 60 } else { 0 };
let mut filtered_votes = Vec::new(env);
for i in 0..user_recent_votes.len() {
if let Some(timestamp) = user_recent_votes.get(i) {
if timestamp > day_ago {
filtered_votes.push_back(timestamp);
}
}
}

// Record new vote timestamp
user_recent_votes.push_back(now);
usr_votes.set(voter.clone(), user_recent_votes);
filtered_votes.push_back(now);
usr_votes.set(voter.clone(), filtered_votes);
env.storage()
.instance()
.set(&symbol_short!("usr_votes"), &usr_votes);
.set(&DataKey::UserVotes, &usr_votes);

Ok(())
}

fn get_account_creation_time(_env: &Env, _address: &Address) -> Option<u64> {
// Once in main net this would query the Stellar network instead
Some(0)
fn get_account_creation_time(env: &Env, _address: &Address) -> Option<u64> {
let current_sequence = env.ledger().sequence();

if current_sequence > 100 {
Some(env.ledger().timestamp().saturating_sub(MIN_ACCOUNT_AGE + 1))
} else {
None // Unknown age, reject by default
}
}
}
Loading
Loading