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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
- Added --ip-net support to create to match the existing behavior in update.
- Use DZ IP for user lookup during status command instead of client IP
- Onchain programs
- Enforce Activated status check before suspending contributor, exchange, location, multicastgroup, and user accounts
- Add on-chain validation to reject CloseAccountDevice when device has active references (reference_count > 0)
- Allow contributor owner to update ops manager key
- Add new arguments on create interface cli command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ pub fn process_suspend_contributor(
}

let mut contributor: Contributor = Contributor::try_from(contributor_account)?;

if contributor.status != ContributorStatus::Activated {
#[cfg(test)]
msg!("{:?}", contributor);
return Err(DoubleZeroError::InvalidStatus.into());
}

contributor.status = ContributorStatus::Suspended;

try_acc_write(&contributor, contributor_account, payer_account, accounts)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ pub fn process_suspend_exchange(
return Err(DoubleZeroError::NotAllowed.into());
}

if exchange.status != ExchangeStatus::Activated {
#[cfg(test)]
msg!("{:?}", exchange);
return Err(DoubleZeroError::InvalidStatus.into());
}

exchange.status = ExchangeStatus::Suspended;

try_acc_write(&exchange, exchange_account, payer_account, accounts)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ pub fn process_suspend_location(

let mut location: Location = Location::try_from(location_account)?;

if location.status != LocationStatus::Activated {
#[cfg(test)]
msg!("{:?}", location);
return Err(DoubleZeroError::InvalidStatus.into());
}

location.status = LocationStatus::Suspended;

try_acc_write(&location, location_account, payer_account, accounts)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ pub fn process_suspend_multicastgroup(
}

let mut multicastgroup: MulticastGroup = MulticastGroup::try_from(multicastgroup_account)?;

if multicastgroup.status != MulticastGroupStatus::Activated {
#[cfg(test)]
msg!("{:?}", multicastgroup);
return Err(DoubleZeroError::InvalidStatus.into());
}

multicastgroup.status = MulticastGroupStatus::Suspended;

try_acc_write(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ pub fn process_suspend_user(
return Err(DoubleZeroError::NotAllowed.into());
}

if user.status != UserStatus::Activated {
#[cfg(test)]
msg!("{:?}", user);
return Err(DoubleZeroError::InvalidStatus.into());
}

user.status = UserStatus::Suspended;

try_acc_write(&user, user_account, payer_account, accounts)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,92 @@ async fn test_contributor() {
println!("✅ Contributor deleted successfully");
println!("🟢 End test_contributor");
}

#[tokio::test]
async fn test_suspend_contributor_from_suspended_fails() {
let (mut banks_client, program_id, payer, recent_blockhash) = init_test().await;

let (program_config_pubkey, _) = get_program_config_pda(&program_id);
let (globalstate_pubkey, _) = get_globalstate_pda(&program_id);
let owner = Pubkey::new_unique();

// Initialize global state
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::InitGlobalState(),
vec![
AccountMeta::new(program_config_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Create a contributor
let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await;
let (contributor_pubkey, _) =
get_contributor_pda(&program_id, globalstate_account.account_index + 1);

execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::CreateContributor(ContributorCreateArgs {
code: "test".to_string(),
}),
vec![
AccountMeta::new(contributor_pubkey, false),
AccountMeta::new(owner, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// First suspend (should succeed)
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SuspendContributor(ContributorSuspendArgs {}),
vec![
AccountMeta::new(contributor_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Verify contributor is suspended
let contributor = get_account_data(&mut banks_client, contributor_pubkey)
.await
.expect("Unable to get Account")
.get_contributor()
.unwrap();
assert_eq!(contributor.status, ContributorStatus::Suspended);

// Second suspend (should fail with InvalidStatus)
let result = try_execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SuspendContributor(ContributorSuspendArgs {}),
vec![
AccountMeta::new(contributor_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

assert!(result.is_err());
let error_string = format!("{:?}", result.unwrap_err());
assert!(
error_string.contains("Custom(7)"),
"Expected InvalidStatus error (Custom(7)), got: {}",
error_string
);
println!("✅ Suspending already-suspended contributor correctly fails with InvalidStatus");
}
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,116 @@ async fn test_exchange_bgp_community_autoassignment() {

println!("🟢 End test_exchange_bgp_community_autoassignment - All assertions passed!");
}

#[tokio::test]
async fn test_suspend_exchange_from_suspended_fails() {
let (mut banks_client, program_id, payer, recent_blockhash) = init_test().await;

let (program_config_pubkey, _) = get_program_config_pda(&program_id);
let (globalconfig_pubkey, _) = get_globalconfig_pda(&program_id);
let (globalstate_pubkey, _) = get_globalstate_pda(&program_id);

// Initialize global state
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::InitGlobalState(),
vec![
AccountMeta::new(program_config_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Initialize global config (required for exchange creation)
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SetGlobalConfig(SetGlobalConfigArgs {
local_asn: 65000,
remote_asn: 65001,
device_tunnel_block: "10.0.0.0/24".parse().unwrap(),
user_tunnel_block: "10.0.0.0/24".parse().unwrap(),
multicastgroup_block: "224.0.0.0/4".parse().unwrap(),
next_bgp_community: None,
}),
vec![
AccountMeta::new(globalconfig_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Create an exchange
let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await;
let (exchange_pubkey, _) = get_exchange_pda(&program_id, globalstate_account.account_index + 1);

execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::CreateExchange(ExchangeCreateArgs {
code: "test".to_string(),
name: "Test Exchange".to_string(),
lat: 1.0,
lng: 2.0,
reserved: 0,
}),
vec![
AccountMeta::new(exchange_pubkey, false),
AccountMeta::new(globalconfig_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// First suspend (should succeed)
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SuspendExchange(ExchangeSuspendArgs {}),
vec![
AccountMeta::new(exchange_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Verify exchange is suspended
let exchange = get_account_data(&mut banks_client, exchange_pubkey)
.await
.expect("Unable to get Account")
.get_exchange()
.unwrap();
assert_eq!(exchange.status, ExchangeStatus::Suspended);

// Second suspend (should fail with InvalidStatus)
let result = try_execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SuspendExchange(ExchangeSuspendArgs {}),
vec![
AccountMeta::new(exchange_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

assert!(result.is_err());
let error_string = format!("{:?}", result.unwrap_err());
assert!(
error_string.contains("Custom(7)"),
"Expected InvalidStatus error (Custom(7)), got: {}",
error_string
);
println!("✅ Suspending already-suspended exchange correctly fails with InvalidStatus");
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,94 @@ async fn test_location() {
println!("✅ Location deleted successfully");
println!("🟢 End test_location");
}

#[tokio::test]
async fn test_suspend_location_from_suspended_fails() {
let (mut banks_client, program_id, payer, recent_blockhash) = init_test().await;

let (program_config_pubkey, _) = get_program_config_pda(&program_id);
let (globalstate_pubkey, _) = get_globalstate_pda(&program_id);

// Initialize global state
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::InitGlobalState(),
vec![
AccountMeta::new(program_config_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Create and suspend a location
let globalstate_account = get_globalstate(&mut banks_client, globalstate_pubkey).await;
let (location_pubkey, _) = get_location_pda(&program_id, globalstate_account.account_index + 1);

execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::CreateLocation(LocationCreateArgs {
code: "test".to_string(),
name: "Test Location".to_string(),
country: "us".to_string(),
lat: 1.0,
lng: 2.0,
loc_id: 0,
}),
vec![
AccountMeta::new(location_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// First suspend (should succeed)
execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SuspendLocation(LocationSuspendArgs {}),
vec![
AccountMeta::new(location_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

// Verify location is suspended
let location = get_account_data(&mut banks_client, location_pubkey)
.await
.expect("Unable to get Account")
.get_location()
.unwrap();
assert_eq!(location.status, LocationStatus::Suspended);

// Second suspend (should fail with InvalidStatus)
let result = try_execute_transaction(
&mut banks_client,
recent_blockhash,
program_id,
DoubleZeroInstruction::SuspendLocation(LocationSuspendArgs {}),
vec![
AccountMeta::new(location_pubkey, false),
AccountMeta::new(globalstate_pubkey, false),
],
&payer,
)
.await;

assert!(result.is_err());
let error_string = format!("{:?}", result.unwrap_err());
assert!(
error_string.contains("Custom(7)"),
"Expected InvalidStatus error (Custom(7)), got: {}",
error_string
);
println!("✅ Suspending already-suspended location correctly fails with InvalidStatus");
}
Loading