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
6 changes: 6 additions & 0 deletions contracts/predictify-hybrid/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub enum Error {
BetsAlreadyPlaced = 111,
/// Insufficient balance
InsufficientBalance = 112,
/// User is blocked by global blacklist
UserBlacklisted = 113,
/// User is not in the required global whitelist
UserNotWhitelisted = 114,
/// Event creator is blocked by global blacklist
CreatorBlacklisted = 115,
// FundsLocked removed to save space

// ===== ORACLE ERRORS =====
Expand Down
39 changes: 39 additions & 0 deletions contracts/predictify-hybrid/src/event_creation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,45 @@ fn test_create_event_unauthorized() {
);
}

#[test]
#[should_panic(expected = "HostError: Error(Contract, #115)")] // Error::CreatorBlacklisted = 115
fn test_create_event_creator_blacklisted_globally() {
let setup = TestSetup::new();
let client = PredictifyHybridClient::new(&setup.env, &setup.contract_id);

// Blacklist the admin as a creator globally
let addrs = vec![&setup.env, setup.admin.clone()];
setup.env.mock_all_auths();
client.add_creators_to_global_blacklist(&setup.admin, &addrs);

let description = String::from_str(&setup.env, "Blacklisted creator event?");
let outcomes = vec![
&setup.env,
String::from_str(&setup.env, "Yes"),
String::from_str(&setup.env, "No"),
];
let end_time = setup.env.ledger().timestamp() + 3600;
let oracle_config = OracleConfig {
provider: OracleProvider::Reflector,
oracle_address: Address::generate(&setup.env),
feed_id: String::from_str(&setup.env, "BTC/USD"),
threshold: 50000,
comparison: String::from_str(&setup.env, "gt"),
};

// Now event creation by this admin should fail due to creator blacklist
client.create_event(
&setup.admin,
&description,
&outcomes,
&end_time,
&oracle_config,
&None,
&0,
&EventVisibility::Public,
);
}

#[test]
#[should_panic(expected = "HostError: Error(Contract, #302)")] // Error::InvalidDuration = 302
fn test_create_event_invalid_end_time() {
Expand Down
146 changes: 146 additions & 0 deletions contracts/predictify-hybrid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ mod reentrancy_guard;
mod resolution;
mod statistics;
mod storage;
mod lists;
mod types;
mod upgrade_manager;
mod utils;
Expand Down Expand Up @@ -715,6 +716,11 @@ impl PredictifyHybrid {
panic_with_error!(env, Error::Unauthorized);
}

// Enforce global creator blacklist (if configured)
if let Err(e) = crate::lists::AccessLists::require_creator_can_create(&env, &admin) {
panic_with_error!(env, e);
}

// Get market configuration for limits
let market_config = crate::config::ConfigManager::get_default_market_config();

Expand Down Expand Up @@ -975,6 +981,138 @@ impl PredictifyHybrid {
Ok(())
}

/// Adds users to the global betting whitelist (admin only).
pub fn add_users_to_global_whitelist(
env: Env,
admin: Address,
addresses: Vec<Address>,
) -> Result<(), Error> {
admin.require_auth();

let stored_admin: Address = env
.storage()
.persistent()
.get(&Symbol::new(&env, "Admin"))
.ok_or(Error::Unauthorized)?;

if admin != stored_admin {
return Err(Error::Unauthorized);
}

crate::lists::AccessLists::add_to_user_whitelist(&env, &addresses);
Ok(())
}

/// Removes users from the global betting whitelist (admin only).
pub fn remove_users_from_global_whitelist(
env: Env,
admin: Address,
addresses: Vec<Address>,
) -> Result<(), Error> {
admin.require_auth();

let stored_admin: Address = env
.storage()
.persistent()
.get(&Symbol::new(&env, "Admin"))
.ok_or(Error::Unauthorized)?;

if admin != stored_admin {
return Err(Error::Unauthorized);
}

crate::lists::AccessLists::remove_from_user_whitelist(&env, &addresses);
Ok(())
}

/// Adds users to the global betting blacklist (admin only).
pub fn add_users_to_global_blacklist(
env: Env,
admin: Address,
addresses: Vec<Address>,
) -> Result<(), Error> {
admin.require_auth();

let stored_admin: Address = env
.storage()
.persistent()
.get(&Symbol::new(&env, "Admin"))
.ok_or(Error::Unauthorized)?;

if admin != stored_admin {
return Err(Error::Unauthorized);
}

crate::lists::AccessLists::add_to_user_blacklist(&env, &addresses);
Ok(())
}

/// Removes users from the global betting blacklist (admin only).
pub fn remove_users_from_global_blacklist(
env: Env,
admin: Address,
addresses: Vec<Address>,
) -> Result<(), Error> {
admin.require_auth();

let stored_admin: Address = env
.storage()
.persistent()
.get(&Symbol::new(&env, "Admin"))
.ok_or(Error::Unauthorized)?;

if admin != stored_admin {
return Err(Error::Unauthorized);
}

crate::lists::AccessLists::remove_from_user_blacklist(&env, &addresses);
Ok(())
}

/// Adds event creators to the global creator blacklist (admin only).
pub fn add_creators_to_global_blacklist(
env: Env,
admin: Address,
addresses: Vec<Address>,
) -> Result<(), Error> {
admin.require_auth();

let stored_admin: Address = env
.storage()
.persistent()
.get(&Symbol::new(&env, "Admin"))
.ok_or(Error::Unauthorized)?;

if admin != stored_admin {
return Err(Error::Unauthorized);
}

crate::lists::AccessLists::add_to_creator_blacklist(&env, &addresses);
Ok(())
}

/// Removes event creators from the global creator blacklist (admin only).
pub fn remove_creators_from_global_blacklist(
env: Env,
admin: Address,
addresses: Vec<Address>,
) -> Result<(), Error> {
admin.require_auth();

let stored_admin: Address = env
.storage()
.persistent()
.get(&Symbol::new(&env, "Admin"))
.ok_or(Error::Unauthorized)?;

if admin != stored_admin {
return Err(Error::Unauthorized);
}

crate::lists::AccessLists::remove_from_creator_blacklist(&env, &addresses);
Ok(())
}

/// Allows users to vote on a market outcome by staking tokens.
///
/// This function enables users to participate in prediction markets by voting
Expand Down Expand Up @@ -1229,6 +1367,10 @@ impl PredictifyHybrid {
}
Err(_) => panic_with_error!(env, Error::InvalidInput),
}
// Enforce global user whitelist/blacklist for betting
if let Err(e) = crate::lists::AccessLists::require_user_can_bet(&env, &user) {
panic_with_error!(env, e);
}
// Use the BetManager to handle the bet placement
match bets::BetManager::place_bet(&env, user.clone(), market_id, outcome, amount) {
Ok(bet) => {
Expand Down Expand Up @@ -1311,6 +1453,10 @@ impl PredictifyHybrid {
if ReentrancyGuard::check_reentrancy_state(&env).is_err() {
panic_with_error!(env, Error::InvalidState);
}
// Enforce global user whitelist/blacklist for betting
if let Err(e) = crate::lists::AccessLists::require_user_can_bet(&env, &user) {
panic_with_error!(env, e);
}
match bets::BetManager::place_bets(&env, user, bets) {
Ok(placed_bets) => {
crate::gas::GasTracker::end_tracking(
Expand Down
130 changes: 130 additions & 0 deletions contracts/predictify-hybrid/src/lists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use soroban_sdk::{Address, Env, Symbol, Vec};

use crate::errors::Error;

/// Simple global access control lists for users and event creators.
///
/// Design:
/// - Empty whitelist means "whitelist disabled" (no restriction).
/// - Blacklist always applies when it contains an address.
/// - For betting, blacklist is checked first, then whitelist (if non-empty).
/// - For event creation, a global creator blacklist is enforced.
pub struct AccessLists;

impl AccessLists {
const USER_WHITELIST_KEY: &'static str = "UserWhitelist";
const USER_BLACKLIST_KEY: &'static str = "UserBlacklist";
const CREATOR_BLACKLIST_KEY: &'static str = "CreatorBlacklist";

fn get_address_list(env: &Env, key: &'static str) -> Vec<Address> {
env.storage()
.persistent()
.get(&Symbol::new(env, key))
.unwrap_or_else(|| Vec::new(env))
}

fn set_address_list(env: &Env, key: &'static str, list: &Vec<Address>) {
env.storage()
.persistent()
.set(&Symbol::new(env, key), list);
}

pub fn add_to_user_whitelist(env: &Env, addresses: &Vec<Address>) {
let mut list = Self::get_address_list(env, Self::USER_WHITELIST_KEY);
for addr in addresses.iter() {
if !list.contains(&addr) {
list.push_back(addr);
}
}
Self::set_address_list(env, Self::USER_WHITELIST_KEY, &list);
}

pub fn remove_from_user_whitelist(env: &Env, addresses: &Vec<Address>) {
let current = Self::get_address_list(env, Self::USER_WHITELIST_KEY);
let mut filtered = Vec::new(env);
for addr in current.iter() {
if !addresses.contains(&addr) {
filtered.push_back(addr);
}
}
Self::set_address_list(env, Self::USER_WHITELIST_KEY, &filtered);
}

pub fn add_to_user_blacklist(env: &Env, addresses: &Vec<Address>) {
let mut list = Self::get_address_list(env, Self::USER_BLACKLIST_KEY);
for addr in addresses.iter() {
if !list.contains(&addr) {
list.push_back(addr);
}
}
Self::set_address_list(env, Self::USER_BLACKLIST_KEY, &list);
}

pub fn remove_from_user_blacklist(env: &Env, addresses: &Vec<Address>) {
let current = Self::get_address_list(env, Self::USER_BLACKLIST_KEY);
let mut filtered = Vec::new(env);
for addr in current.iter() {
if !addresses.contains(&addr) {
filtered.push_back(addr);
}
}
Self::set_address_list(env, Self::USER_BLACKLIST_KEY, &filtered);
}

pub fn add_to_creator_blacklist(env: &Env, addresses: &Vec<Address>) {
let mut list = Self::get_address_list(env, Self::CREATOR_BLACKLIST_KEY);
for addr in addresses.iter() {
if !list.contains(&addr) {
list.push_back(addr);
}
}
Self::set_address_list(env, Self::CREATOR_BLACKLIST_KEY, &list);
}

pub fn remove_from_creator_blacklist(env: &Env, addresses: &Vec<Address>) {
let current = Self::get_address_list(env, Self::CREATOR_BLACKLIST_KEY);
let mut filtered = Vec::new(env);
for addr in current.iter() {
if !addresses.contains(&addr) {
filtered.push_back(addr);
}
}
Self::set_address_list(env, Self::CREATOR_BLACKLIST_KEY, &filtered);
}

/// Enforce global user whitelist/blacklist for betting.
///
/// Logic:
/// - If user is in global blacklist → `Error::UserBlacklisted`
/// - Else if whitelist is empty → allowed
/// - Else if whitelist contains user → allowed
/// - Else → `Error::UserNotWhitelisted`
pub fn require_user_can_bet(env: &Env, user: &Address) -> Result<(), Error> {
let blacklist = Self::get_address_list(env, Self::USER_BLACKLIST_KEY);
if blacklist.contains(user) {
return Err(Error::UserBlacklisted);
}

let whitelist = Self::get_address_list(env, Self::USER_WHITELIST_KEY);
if whitelist.is_empty() {
return Ok(());
}

if whitelist.contains(user) {
Ok(())
} else {
Err(Error::UserNotWhitelisted)
}
}

/// Enforce global creator blacklist for event creation.
pub fn require_creator_can_create(env: &Env, creator: &Address) -> Result<(), Error> {
let blacklist = Self::get_address_list(env, Self::CREATOR_BLACKLIST_KEY);
if blacklist.contains(creator) {
Err(Error::CreatorBlacklisted)
} else {
Ok(())
}
}
}

Loading
Loading