mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-25 23:23:11 +00:00
fix: address review feedback, persist group data in wallet storage
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
#![expect(
|
||||
clippy::tests_outside_test_module,
|
||||
reason = "Integration test file, not inside a #[cfg(test)] module"
|
||||
)]
|
||||
|
||||
//! Shared account integration tests.
|
||||
//!
|
||||
//! Demonstrates:
|
||||
//! 1. Group creation and GMS distribution via seal/unseal.
|
||||
//! 2. Shared regular private account creation via `--for-gms`.
|
||||
//! 3. Funding a shared account from a public account.
|
||||
//! 4. Syncing discovers the funded shared account state.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
|
||||
use log::info;
|
||||
use tokio::test;
|
||||
use wallet::cli::{
|
||||
Command, SubcommandReturnValue,
|
||||
account::{AccountSubcommand, NewSubcommand},
|
||||
group::GroupSubcommand,
|
||||
programs::native_token_transfer::AuthTransferSubcommand,
|
||||
};
|
||||
|
||||
/// Create a group, create a shared account from it, and verify registration.
|
||||
#[test]
|
||||
async fn group_create_and_shared_account_registration() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
// Create a group
|
||||
let command = Command::Group(GroupSubcommand::New {
|
||||
name: "test-group".to_string(),
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Verify group exists
|
||||
assert!(
|
||||
ctx.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.group_key_holder("test-group")
|
||||
.is_some()
|
||||
);
|
||||
|
||||
// Create a shared regular private account from the group
|
||||
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
|
||||
cci: None,
|
||||
label: Some("shared-acc".to_string()),
|
||||
for_gms: Some("test-group".to_string()),
|
||||
pda: false,
|
||||
seed: None,
|
||||
program_id: None,
|
||||
}));
|
||||
|
||||
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: shared_account_id,
|
||||
} = result
|
||||
else {
|
||||
anyhow::bail!("Expected RegisterAccount return value");
|
||||
};
|
||||
|
||||
// Verify shared account is registered in storage
|
||||
let shared_private_accounts = &ctx.wallet().storage().user_data.shared_private_accounts;
|
||||
let entry = shared_private_accounts
|
||||
.get(&shared_account_id)
|
||||
.context("Shared account not found in storage")?;
|
||||
assert_eq!(entry.group_label, "test-group");
|
||||
assert!(entry.pda_seed.is_none());
|
||||
|
||||
info!("Shared account registered: {shared_account_id}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// GMS seal/unseal round-trip: export GMS, re-import under a new name, verify key agreement.
|
||||
#[test]
|
||||
async fn group_export_import_key_agreement() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
// Create a group
|
||||
let command = Command::Group(GroupSubcommand::New {
|
||||
name: "alice-group".to_string(),
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Export the GMS
|
||||
let holder = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.group_key_holder("alice-group")
|
||||
.context("Group not found")?;
|
||||
let gms_hex = hex::encode(holder.dangerous_raw_gms());
|
||||
|
||||
// Import under a different name (simulating Bob receiving the GMS)
|
||||
let command = Command::Group(GroupSubcommand::Import {
|
||||
name: "bob-copy".to_string(),
|
||||
gms: gms_hex,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Both derive the same keys for the same tag
|
||||
let alice_holder = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.group_key_holder("alice-group")
|
||||
.unwrap();
|
||||
let bob_holder = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.group_key_holder("bob-copy")
|
||||
.unwrap();
|
||||
|
||||
let tag = [42_u8; 32];
|
||||
let alice_npk = alice_holder
|
||||
.derive_keys_for_shared_account(&tag)
|
||||
.generate_nullifier_public_key();
|
||||
let bob_npk = bob_holder
|
||||
.derive_keys_for_shared_account(&tag)
|
||||
.generate_nullifier_public_key();
|
||||
|
||||
assert_eq!(
|
||||
alice_npk, bob_npk,
|
||||
"Key agreement: same GMS produces same keys"
|
||||
);
|
||||
|
||||
info!("Key agreement verified");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fund a shared account from a public account via auth-transfer, then sync.
|
||||
#[test]
|
||||
async fn fund_shared_account_from_public() -> Result<()> {
|
||||
let mut ctx = TestContext::new().await?;
|
||||
|
||||
// Create group and shared account
|
||||
let command = Command::Group(GroupSubcommand::New {
|
||||
name: "fund-group".to_string(),
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
|
||||
cci: None,
|
||||
label: None,
|
||||
for_gms: Some("fund-group".to_string()),
|
||||
pda: false,
|
||||
seed: None,
|
||||
program_id: None,
|
||||
}));
|
||||
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
let SubcommandReturnValue::RegisterAccount {
|
||||
account_id: shared_id,
|
||||
} = result
|
||||
else {
|
||||
anyhow::bail!("Expected RegisterAccount return value");
|
||||
};
|
||||
|
||||
// Initialize the shared account under auth-transfer
|
||||
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
|
||||
account_id: Some(format!("Private/{shared_id}")),
|
||||
account_label: None,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Fund from a public account
|
||||
let from_public = ctx.existing_public_accounts()[0];
|
||||
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
|
||||
from: Some(format_public_account_id(from_public)),
|
||||
from_label: None,
|
||||
to: Some(format!("Private/{shared_id}")),
|
||||
to_label: None,
|
||||
to_npk: None,
|
||||
to_vpk: None,
|
||||
to_identifier: None,
|
||||
amount: 100,
|
||||
});
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
||||
|
||||
// Sync private accounts
|
||||
let command = Command::Account(AccountSubcommand::SyncPrivate);
|
||||
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
|
||||
|
||||
// Verify the shared account was updated
|
||||
let entry = ctx
|
||||
.wallet()
|
||||
.storage()
|
||||
.user_data
|
||||
.shared_private_accounts
|
||||
.get(&shared_id)
|
||||
.context("Shared account not found after sync")?;
|
||||
|
||||
info!(
|
||||
"Shared account balance after funding: {}",
|
||||
entry.account.balance
|
||||
);
|
||||
assert_eq!(
|
||||
entry.account.balance, 100,
|
||||
"Shared account should have received 100"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user