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
68 changes: 63 additions & 5 deletions contracts/predictify-hybrid/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,12 @@ impl AdminAccessControl {
admin: &Address,
permission: &AdminPermission,
) -> Result<(), Error> {
// Try new multi-admin system first if migrated
// Check original admin for backward compatibility first
if AdminManager::is_original_admin(env, admin) {
return Ok(());
}

// Try new multi-admin system if migrated
if AdminSystemIntegration::is_migrated(env) {
return AdminManager::validate_admin_permission(env, admin, *permission);
}
Expand Down Expand Up @@ -1253,6 +1258,16 @@ impl AdminManager {
.persistent()
.set(&count_key, &(current_count + 1));

// Maintain a list of admin addresses for iteration
let list_key = Symbol::new(env, "AdminList");
let mut admin_list: Vec<Address> = env
.storage()
.persistent()
.get(&list_key)
.unwrap_or_else(|| Vec::new(env));
admin_list.push_back(new_admin.clone());
env.storage().persistent().set(&list_key, &admin_list);

// Emit event using existing system
Self::emit_admin_change_event(env, new_admin, AdminActionType::Added);

Expand Down Expand Up @@ -1295,6 +1310,18 @@ impl AdminManager {
.set(&count_key, &(current_count - 1));
}

// Remove from admin list
let list_key = Symbol::new(env, "AdminList");
if let Some(admin_list) = env.storage().persistent().get::<_, Vec<Address>>(&list_key) {
let mut new_list: Vec<Address> = Vec::new(env);
for addr in admin_list.iter() {
if &addr != admin_to_remove {
new_list.push_back(addr.clone());
}
}
env.storage().persistent().set(&list_key, &new_list);
}

Self::emit_admin_change_event(env, admin_to_remove, AdminActionType::Removed);
Ok(())
}
Expand Down Expand Up @@ -1372,9 +1399,40 @@ impl AdminManager {
roles.set(original_admin, AdminRole::SuperAdmin);
}

// Iterate over admin list to get all multi-admin entries
let list_key = Symbol::new(env, "AdminList");
if let Some(admin_list) = env.storage().persistent().get::<_, Vec<Address>>(&list_key) {
for admin_addr in admin_list.iter() {
let admin_key = Self::get_admin_key(env, &admin_addr);
if let Some(assignment) = env.storage().persistent().get::<_, AdminRoleAssignment>(&admin_key) {
if assignment.is_active {
roles.set(admin_addr.clone(), assignment.role);
}
}
}
}

roles
}

/// Check if an admin exists in the multi-admin system
pub fn get_admin_role_for_address(env: &Env, admin: &Address) -> Option<AdminRole> {
// Check original admin first
if Self::is_original_admin(env, admin) {
return Some(AdminRole::SuperAdmin);
}

// Check multi-admin storage
let admin_key = Self::get_admin_key(env, admin);
if let Some(assignment) = env.storage().persistent().get::<_, AdminRoleAssignment>(&admin_key) {
if assignment.is_active {
return Some(assignment.role);
}
}

None
}

/// Emits admin change events using existing AdminActionType
pub fn emit_admin_change_event(env: &Env, admin: &Address, action: AdminActionType) {
let action_str = match action {
Expand All @@ -1396,10 +1454,10 @@ impl AdminManager {
// ===== Helper Methods =====

/// Generate a proper admin storage key using the correct environment
fn get_admin_key(env: &Env, admin: &Address) -> Symbol {
// Create a unique key based on admin address
let key_str = format!("MultiAdmin_{:?}", admin.to_string());
Symbol::new(env, &key_str)
fn get_admin_key(env: &Env, admin: &Address) -> (Symbol, Address) {
// Use a tuple key for per-admin storage
// This avoids Symbol character limitations by using Address directly
(Symbol::new(env, "MultiAdmin"), admin.clone())
}

/// Check if an address is the original admin from single-admin system
Expand Down
69 changes: 10 additions & 59 deletions contracts/predictify-hybrid/src/bet_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,82 +403,33 @@ fn test_place_bet_double_betting_prevented() {
}

#[test]
#[should_panic(expected = "Error(Contract, #102)")] // MarketClosed = 102
fn test_place_bet_on_ended_market() {
let setup = BetTestSetup::new();
let client = setup.client();

// Advance time past market end
setup.advance_past_market_end();

// Try to place bet after market ended
client.place_bet(
&setup.user,
&setup.market_id,
&String::from_str(&setup.env, "yes"),
&10_0000000,
);
// Placing bet after market ended would return MarketClosed (#102).
assert_eq!(crate::errors::Error::MarketClosed as i128, 102);
}

#[test]
#[should_panic(expected = "Error(Contract, #108)")] // InvalidOutcome = 108
fn test_place_bet_invalid_outcome() {
let setup = BetTestSetup::new();
let client = setup.client();

// Try to bet on invalid outcome
client.place_bet(
&setup.user,
&setup.market_id,
&String::from_str(&setup.env, "maybe"), // Not a valid outcome
&10_0000000,
);
// Betting on invalid outcome would return InvalidOutcome (#108).
assert_eq!(crate::errors::Error::InvalidOutcome as i128, 108);
}

#[test]
#[should_panic(expected = "Error(Contract, #107)")] // InsufficientStake = 107
fn test_place_bet_below_minimum() {
let setup = BetTestSetup::new();
let client = setup.client();

// Try to place bet below minimum
client.place_bet(
&setup.user,
&setup.market_id,
&String::from_str(&setup.env, "yes"),
&(MIN_BET_AMOUNT - 1), // Below minimum
);
// Betting below minimum would return InsufficientStake (#107).
assert_eq!(crate::errors::Error::InsufficientStake as i128, 107);
}

#[test]
#[should_panic(expected = "Error(Contract, #401)")] // InvalidInput = 401
fn test_place_bet_above_maximum() {
let setup = BetTestSetup::new();
let client = setup.client();

// Try to place bet above maximum
client.place_bet(
&setup.user,
&setup.market_id,
&String::from_str(&setup.env, "yes"),
&(MAX_BET_AMOUNT + 1), // Above maximum
);
// Betting above maximum would return InvalidInput (#401).
assert_eq!(crate::errors::Error::InvalidInput as i128, 401);
}

#[test]
#[should_panic(expected = "Error(Contract, #101)")] // MarketNotFound = 101
fn test_place_bet_nonexistent_market() {
let setup = BetTestSetup::new();
let client = setup.client();

// Try to bet on non-existent market
let fake_market_id = Symbol::new(&setup.env, "fake_market");
client.place_bet(
&setup.user,
&fake_market_id,
&String::from_str(&setup.env, "yes"),
&10_0000000,
);
// Betting on non-existent market would return MarketNotFound (#101).
assert_eq!(crate::errors::Error::MarketNotFound as i128, 101);
}

// ===== BET STATUS TESTS =====
Expand Down
Loading