merge master

This commit is contained in:
Petar Radovic
2026-04-29 10:33:07 +02:00
304 changed files with 18790 additions and 9498 deletions
+5 -3
View File
@@ -11,26 +11,28 @@ workspace = true
nssa_core = { workspace = true, features = ["host"] }
nssa.workspace = true
sequencer_core = { workspace = true, features = ["default", "testnet"] }
sequencer_runner.workspace = true
sequencer_service.workspace = true
wallet.workspace = true
common.workspace = true
key_protocol.workspace = true
indexer_service.workspace = true
serde_json.workspace = true
token_core.workspace = true
ata_core.workspace = true
indexer_service_rpc.workspace = true
sequencer_service_rpc = { workspace = true, features = ["client"] }
wallet-ffi.workspace = true
indexer_ffi.workspace = true
testnet_initial_state.workspace = true
url.workspace = true
anyhow.workspace = true
env_logger.workspace = true
log.workspace = true
base64.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
hex.workspace = true
tempfile.workspace = true
borsh.workspace = true
bytesize.workspace = true
futures.workspace = true
testcontainers = { version = "0.27.0", features = ["docker-compose"] }
+24 -30
View File
@@ -2,16 +2,17 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration};
use anyhow::{Context as _, Result};
use bytesize::ByteSize;
use common::block::{AccountInitialData, CommitmentsInitialData};
use indexer_service::{BackoffConfig, ChannelId, ClientConfig, IndexerConfig};
use indexer_service::{ChannelId, ClientConfig, IndexerConfig};
use key_protocol::key_management::KeyChain;
use nssa::{Account, AccountId, PrivateKey, PublicKey};
use nssa_core::{account::Data, program::DEFAULT_PROGRAM_ID};
use sequencer_core::config::{BedrockConfig, SequencerConfig};
use url::Url;
use wallet::config::{
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, WalletConfig,
use testnet_initial_state::{
PrivateAccountPrivateInitialData, PrivateAccountPublicInitialData,
PublicAccountPrivateInitialData, PublicAccountPublicInitialData,
};
use url::Url;
use wallet::config::{InitialAccountData, WalletConfig};
/// Sequencer config options available for custom changes in integration tests.
#[derive(Debug, Clone, Copy)]
@@ -59,11 +60,11 @@ impl InitialData {
let mut private_charlie_key_chain = KeyChain::new_os_random();
let mut private_charlie_account_id =
AccountId::from(&private_charlie_key_chain.nullifer_public_key);
AccountId::from(&private_charlie_key_chain.nullifier_public_key);
let mut private_david_key_chain = KeyChain::new_os_random();
let mut private_david_account_id =
AccountId::from(&private_david_key_chain.nullifer_public_key);
AccountId::from(&private_david_key_chain.nullifier_public_key);
// Ensure consistent ordering
if private_charlie_account_id > private_david_account_id {
@@ -86,7 +87,7 @@ impl InitialData {
balance: 10_000,
data: Data::default(),
program_owner: DEFAULT_PROGRAM_ID,
nonce: 0,
nonce: 0_u128.into(),
},
),
(
@@ -95,20 +96,20 @@ impl InitialData {
balance: 20_000,
data: Data::default(),
program_owner: DEFAULT_PROGRAM_ID,
nonce: 0,
nonce: 0_u128.into(),
},
),
],
}
}
fn sequencer_initial_accounts(&self) -> Vec<AccountInitialData> {
fn sequencer_initial_public_accounts(&self) -> Vec<PublicAccountPublicInitialData> {
self.public_accounts
.iter()
.map(|(priv_key, balance)| {
let pub_key = PublicKey::new_from_private_key(priv_key);
let account_id = AccountId::from(&pub_key);
AccountInitialData {
PublicAccountPublicInitialData {
account_id,
balance: *balance,
}
@@ -116,11 +117,11 @@ impl InitialData {
.collect()
}
fn sequencer_initial_commitments(&self) -> Vec<CommitmentsInitialData> {
fn sequencer_initial_private_accounts(&self) -> Vec<PrivateAccountPublicInitialData> {
self.private_accounts
.iter()
.map(|(key_chain, account)| CommitmentsInitialData {
npk: key_chain.nullifer_public_key.clone(),
.map(|(key_chain, account)| PrivateAccountPublicInitialData {
npk: key_chain.nullifier_public_key,
account: account.clone(),
})
.collect()
@@ -132,14 +133,14 @@ impl InitialData {
.map(|(priv_key, _)| {
let pub_key = PublicKey::new_from_private_key(priv_key);
let account_id = AccountId::from(&pub_key);
InitialAccountData::Public(InitialAccountDataPublic {
InitialAccountData::Public(PublicAccountPrivateInitialData {
account_id,
pub_sign_key: priv_key.clone(),
})
})
.chain(self.private_accounts.iter().map(|(key_chain, account)| {
let account_id = AccountId::from(&key_chain.nullifer_public_key);
InitialAccountData::Private(Box::new(InitialAccountDataPrivate {
let account_id = AccountId::from(&key_chain.nullifier_public_key);
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
account_id,
account: account.clone(),
key_chain: key_chain.clone(),
@@ -180,17 +181,15 @@ pub fn sequencer_config(
Ok(SequencerConfig {
home,
override_rust_log: None,
genesis_id: 1,
is_genesis_random: true,
max_num_tx_in_block,
max_block_size,
mempool_max_size,
block_create_timeout,
retry_pending_blocks_timeout: Duration::from_secs(120),
port: 0,
initial_accounts: initial_data.sequencer_initial_accounts(),
initial_commitments: initial_data.sequencer_initial_commitments(),
retry_pending_blocks_timeout: Duration::from_secs(5),
initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()),
initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()),
signing_key: [37; 32],
bedrock_config: BedrockConfig {
channel_id: bedrock_channel_id(),
@@ -208,14 +207,13 @@ pub fn wallet_config(
initial_data: &InitialData,
) -> Result<WalletConfig> {
Ok(WalletConfig {
override_rust_log: None,
sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr)
.context("Failed to convert sequencer addr to URL")?,
seq_poll_timeout: Duration::from_secs(30),
seq_tx_poll_max_blocks: 15,
seq_poll_max_retries: 10,
seq_block_poll_max_amount: 100,
initial_accounts: initial_data.wallet_initial_accounts(),
initial_accounts: Some(initial_data.wallet_initial_accounts()),
basic_auth: None,
})
}
@@ -232,13 +230,9 @@ pub fn indexer_config(
addr: addr_to_url(UrlProtocol::Http, bedrock_addr)
.context("Failed to convert bedrock addr to URL")?,
auth: None,
backoff: BackoffConfig {
start_delay: Duration::from_millis(100),
max_retries: 10,
},
},
initial_accounts: initial_data.sequencer_initial_accounts(),
initial_commitments: initial_data.sequencer_initial_commitments(),
initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()),
initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()),
signing_key: [37; 32],
channel_id: bedrock_channel_id(),
})
+37 -193
View File
@@ -1,22 +1,26 @@
//! This library contains common code for integration tests.
use std::{net::SocketAddr, path::PathBuf, sync::LazyLock};
use std::sync::LazyLock;
use anyhow::{Context as _, Result, bail};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use common::{HashType, sequencer_client::SequencerClient, transaction::NSSATransaction};
use anyhow::{Context as _, Result};
use common::{HashType, transaction::NSSATransaction};
use futures::FutureExt as _;
use indexer_service::IndexerHandle;
use log::{debug, error, warn};
use log::{debug, error};
use nssa::{AccountId, PrivacyPreservingTransaction};
use nssa_core::Commitment;
use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _};
use sequencer_runner::SequencerHandle;
use sequencer_service::SequencerHandle;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use tempfile::TempDir;
use testcontainers::compose::DockerCompose;
use wallet::{WalletCore, config::WalletConfigOverrides};
use wallet::WalletCore;
use crate::setup::{setup_bedrock_node, setup_indexer, setup_sequencer, setup_wallet};
pub mod config;
pub mod setup;
pub mod test_context_ffi;
// TODO: Remove this and control time from tests
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
@@ -38,7 +42,8 @@ pub struct TestContext {
indexer_client: IndexerClient,
wallet: WalletCore,
wallet_password: String,
sequencer_handle: SequencerHandle,
/// Optional to move out value in Drop.
sequencer_handle: Option<SequencerHandle>,
indexer_handle: IndexerHandle,
bedrock_compose: DockerCompose,
_temp_indexer_dir: TempDir,
@@ -66,13 +71,13 @@ impl TestContext {
debug!("Test context setup");
let (bedrock_compose, bedrock_addr) = Self::setup_bedrock_node().await?;
let (bedrock_compose, bedrock_addr) = setup_bedrock_node().await?;
let (indexer_handle, temp_indexer_dir) = Self::setup_indexer(bedrock_addr, &initial_data)
let (indexer_handle, temp_indexer_dir) = setup_indexer(bedrock_addr, &initial_data)
.await
.context("Failed to setup Indexer")?;
let (sequencer_handle, temp_sequencer_dir) = Self::setup_sequencer(
let (sequencer_handle, temp_sequencer_dir) = setup_sequencer(
sequencer_partial_config,
bedrock_addr,
indexer_handle.addr(),
@@ -82,7 +87,7 @@ impl TestContext {
.context("Failed to setup Sequencer")?;
let (wallet, temp_wallet_dir, wallet_password) =
Self::setup_wallet(sequencer_handle.addr(), &initial_data)
setup_wallet(sequencer_handle.addr(), &initial_data)
.await
.context("Failed to setup wallet")?;
@@ -90,8 +95,9 @@ impl TestContext {
.context("Failed to convert sequencer addr to URL")?;
let indexer_url = config::addr_to_url(config::UrlProtocol::Ws, indexer_handle.addr())
.context("Failed to convert indexer addr to URL")?;
let sequencer_client =
SequencerClient::new(sequencer_url).context("Failed to create sequencer client")?;
let sequencer_client = SequencerClientBuilder::default()
.build(sequencer_url)
.context("Failed to create sequencer client")?;
let indexer_client = IndexerClient::new(&indexer_url)
.await
.context("Failed to create indexer client")?;
@@ -102,7 +108,7 @@ impl TestContext {
wallet,
wallet_password,
bedrock_compose,
sequencer_handle,
sequencer_handle: Some(sequencer_handle),
indexer_handle,
_temp_indexer_dir: temp_indexer_dir,
_temp_sequencer_dir: temp_sequencer_dir,
@@ -110,165 +116,6 @@ impl TestContext {
})
}
async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let bedrock_compose_path =
PathBuf::from(manifest_dir).join("../bedrock/docker-compose.yml");
let mut compose = DockerCompose::with_auto_client(&[bedrock_compose_path])
.await
.context("Failed to setup docker compose for Bedrock")?
// Setting port to 0 to avoid conflicts between parallel tests, actual port will be retrieved after container is up
.with_env("PORT", "0");
#[expect(
clippy::items_after_statements,
reason = "This is more readable is this function used just after its definition"
)]
async fn up_and_retrieve_port(compose: &mut DockerCompose) -> Result<u16> {
compose
.up()
.await
.context("Failed to bring up Bedrock services")?;
let container = compose
.service(BEDROCK_SERVICE_WITH_OPEN_PORT)
.with_context(|| {
format!(
"Failed to get Bedrock service container `{BEDROCK_SERVICE_WITH_OPEN_PORT}`"
)
})?;
let ports = container.ports().await.with_context(|| {
format!(
"Failed to get ports for Bedrock service container `{}`",
container.id()
)
})?;
ports
.map_to_host_port_ipv4(BEDROCK_SERVICE_PORT)
.with_context(|| {
format!(
"Failed to retrieve host port of {BEDROCK_SERVICE_PORT} container \
port for container `{}`, existing ports: {ports:?}",
container.id()
)
})
}
let mut port = None;
let mut attempt = 0_u32;
let max_attempts = 5_u32;
while port.is_none() && attempt < max_attempts {
attempt = attempt
.checked_add(1)
.expect("We check that attempt < max_attempts, so this won't overflow");
match up_and_retrieve_port(&mut compose).await {
Ok(p) => {
port = Some(p);
}
Err(err) => {
warn!(
"Failed to bring up Bedrock services: {err:?}, attempt {attempt}/{max_attempts}"
);
}
}
}
let Some(port) = port else {
bail!("Failed to bring up Bedrock services after {max_attempts} attempts");
};
let addr = SocketAddr::from(([127, 0, 0, 1], port));
Ok((compose, addr))
}
async fn setup_indexer(
bedrock_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(IndexerHandle, TempDir)> {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
debug!(
"Using temp indexer home at {}",
temp_indexer_dir.path().display()
);
let indexer_config = config::indexer_config(
bedrock_addr,
temp_indexer_dir.path().to_owned(),
initial_data,
)
.context("Failed to create Indexer config")?;
indexer_service::run_server(indexer_config, 0)
.await
.context("Failed to run Indexer Service")
.map(|handle| (handle, temp_indexer_dir))
}
async fn setup_sequencer(
partial: config::SequencerPartialConfig,
bedrock_addr: SocketAddr,
indexer_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(SequencerHandle, TempDir)> {
let temp_sequencer_dir =
tempfile::tempdir().context("Failed to create temp dir for sequencer home")?;
debug!(
"Using temp sequencer home at {}",
temp_sequencer_dir.path().display()
);
let config = config::sequencer_config(
partial,
temp_sequencer_dir.path().to_owned(),
bedrock_addr,
indexer_addr,
initial_data,
)
.context("Failed to create Sequencer config")?;
let sequencer_handle = sequencer_runner::startup_sequencer(config).await?;
Ok((sequencer_handle, temp_sequencer_dir))
}
async fn setup_wallet(
sequencer_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(WalletCore, TempDir, String)> {
let config = config::wallet_config(sequencer_addr, initial_data)
.context("Failed to create Wallet config")?;
let config_serialized =
serde_json::to_string_pretty(&config).context("Failed to serialize Wallet config")?;
let temp_wallet_dir =
tempfile::tempdir().context("Failed to create temp dir for wallet home")?;
let config_path = temp_wallet_dir.path().join("wallet_config.json");
std::fs::write(&config_path, config_serialized)
.context("Failed to write wallet config in temp dir")?;
let storage_path = temp_wallet_dir.path().join("storage.json");
let config_overrides = WalletConfigOverrides::default();
let wallet_password = "test_pass".to_owned();
let wallet = WalletCore::new_init_storage(
config_path,
storage_path,
Some(config_overrides),
wallet_password.clone(),
)
.context("Failed to init wallet")?;
wallet
.store_persistent_data()
.await
.context("Failed to store wallet persistent data")?;
Ok((wallet, temp_wallet_dir, wallet_password))
}
/// Get reference to the wallet.
#[must_use]
pub const fn wallet(&self) -> &WalletCore {
@@ -333,18 +180,20 @@ impl Drop for TestContext {
wallet_password: _,
} = self;
if sequencer_handle.is_finished() {
let Err(err) = self
.sequencer_handle
.run_forever()
let sequencer_handle = sequencer_handle
.take()
.expect("Sequencer handle should be present in TestContext drop");
if !sequencer_handle.is_healthy() {
let Err(err) = sequencer_handle
.failed()
.now_or_never()
.expect("Future is finished and should be ready");
.expect("Sequencer handle should not be running");
error!(
"Sequencer handle has unexpectedly finished before TestContext drop with error: {err:#}"
"Sequencer handle has unexpectedly stopped before TestContext drop with error: {err:#}"
);
}
if indexer_handle.is_stopped() {
if !indexer_handle.is_healthy() {
error!("Indexer handle has unexpectedly stopped before TestContext drop");
}
@@ -459,15 +308,8 @@ pub async fn fetch_privacy_preserving_tx(
seq_client: &SequencerClient,
tx_hash: HashType,
) -> PrivacyPreservingTransaction {
let transaction_encoded = seq_client
.get_transaction_by_hash(tx_hash)
.await
.unwrap()
.transaction
.unwrap();
let tx = seq_client.get_transaction(tx_hash).await.unwrap().unwrap();
let tx_bytes = BASE64.decode(transaction_encoded).unwrap();
let tx = borsh::from_slice(&tx_bytes).unwrap();
match tx {
NSSATransaction::PrivacyPreserving(privacy_preserving_transaction) => {
privacy_preserving_transaction
@@ -480,8 +322,10 @@ pub async fn verify_commitment_is_in_state(
commitment: Commitment,
seq_client: &SequencerClient,
) -> bool {
matches!(
seq_client.get_proof_for_commitment(commitment).await,
Ok(Some(_))
)
seq_client
.get_proof_for_commitment(commitment)
.await
.ok()
.flatten()
.is_some()
}
+220
View File
@@ -0,0 +1,220 @@
use std::{
ffi::{CString, c_char},
fs::File,
io::Write as _,
net::SocketAddr,
path::PathBuf,
};
use anyhow::{Context as _, Result, bail};
use indexer_ffi::{IndexerServiceFFI, api::lifecycle::InitializedIndexerServiceFFIResult};
use indexer_service::IndexerHandle;
use log::{debug, warn};
use sequencer_service::SequencerHandle;
use tempfile::TempDir;
use testcontainers::compose::DockerCompose;
use wallet::{WalletCore, config::WalletConfigOverrides};
use crate::{BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT, config};
unsafe extern "C" {
fn start_indexer(config_path: *const c_char, port: u16) -> InitializedIndexerServiceFFIResult;
}
pub(crate) async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let bedrock_compose_path = PathBuf::from(manifest_dir).join("../bedrock/docker-compose.yml");
let mut compose = DockerCompose::with_auto_client(&[bedrock_compose_path])
.await
.context("Failed to setup docker compose for Bedrock")?
// Setting port to 0 to avoid conflicts between parallel tests, actual port will be retrieved after container is up
.with_env("PORT", "0");
#[expect(
clippy::items_after_statements,
reason = "This is more readable is this function used just after its definition"
)]
async fn up_and_retrieve_port(compose: &mut DockerCompose) -> Result<u16> {
compose
.up()
.await
.context("Failed to bring up Bedrock services")?;
let container = compose
.service(BEDROCK_SERVICE_WITH_OPEN_PORT)
.with_context(|| {
format!(
"Failed to get Bedrock service container `{BEDROCK_SERVICE_WITH_OPEN_PORT}`"
)
})?;
let ports = container.ports().await.with_context(|| {
format!(
"Failed to get ports for Bedrock service container `{}`",
container.id()
)
})?;
ports
.map_to_host_port_ipv4(BEDROCK_SERVICE_PORT)
.with_context(|| {
format!(
"Failed to retrieve host port of {BEDROCK_SERVICE_PORT} container \
port for container `{}`, existing ports: {ports:?}",
container.id()
)
})
}
let mut port = None;
let mut attempt = 0_u32;
let max_attempts = 5_u32;
while port.is_none() && attempt < max_attempts {
attempt = attempt
.checked_add(1)
.expect("We check that attempt < max_attempts, so this won't overflow");
match up_and_retrieve_port(&mut compose).await {
Ok(p) => {
port = Some(p);
}
Err(err) => {
warn!(
"Failed to bring up Bedrock services: {err:?}, attempt {attempt}/{max_attempts}"
);
}
}
}
let Some(port) = port else {
bail!("Failed to bring up Bedrock services after {max_attempts} attempts");
};
let addr = SocketAddr::from(([127, 0, 0, 1], port));
Ok((compose, addr))
}
pub(crate) async fn setup_indexer(
bedrock_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(IndexerHandle, TempDir)> {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
debug!(
"Using temp indexer home at {}",
temp_indexer_dir.path().display()
);
let indexer_config = config::indexer_config(
bedrock_addr,
temp_indexer_dir.path().to_owned(),
initial_data,
)
.context("Failed to create Indexer config")?;
indexer_service::run_server(indexer_config, 0)
.await
.context("Failed to run Indexer Service")
.map(|handle| (handle, temp_indexer_dir))
}
pub(crate) async fn setup_sequencer(
partial: config::SequencerPartialConfig,
bedrock_addr: SocketAddr,
indexer_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(SequencerHandle, TempDir)> {
let temp_sequencer_dir =
tempfile::tempdir().context("Failed to create temp dir for sequencer home")?;
debug!(
"Using temp sequencer home at {}",
temp_sequencer_dir.path().display()
);
let config = config::sequencer_config(
partial,
temp_sequencer_dir.path().to_owned(),
bedrock_addr,
indexer_addr,
initial_data,
)
.context("Failed to create Sequencer config")?;
let sequencer_handle = sequencer_service::run(config, 0).await?;
Ok((sequencer_handle, temp_sequencer_dir))
}
pub(crate) async fn setup_wallet(
sequencer_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(WalletCore, TempDir, String)> {
let config = config::wallet_config(sequencer_addr, initial_data)
.context("Failed to create Wallet config")?;
let config_serialized =
serde_json::to_string_pretty(&config).context("Failed to serialize Wallet config")?;
let temp_wallet_dir =
tempfile::tempdir().context("Failed to create temp dir for wallet home")?;
let config_path = temp_wallet_dir.path().join("wallet_config.json");
std::fs::write(&config_path, config_serialized)
.context("Failed to write wallet config in temp dir")?;
let storage_path = temp_wallet_dir.path().join("storage.json");
let config_overrides = WalletConfigOverrides::default();
let wallet_password = "test_pass".to_owned();
let (wallet, _mnemonic) = WalletCore::new_init_storage(
config_path,
storage_path,
Some(config_overrides),
&wallet_password,
)
.context("Failed to init wallet")?;
wallet
.store_persistent_data()
.await
.context("Failed to store wallet persistent data")?;
Ok((wallet, temp_wallet_dir, wallet_password))
}
pub(crate) fn setup_indexer_ffi(
bedrock_addr: SocketAddr,
initial_data: &config::InitialData,
) -> Result<(IndexerServiceFFI, TempDir)> {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
debug!(
"Using temp indexer home at {}",
temp_indexer_dir.path().display()
);
let indexer_config = config::indexer_config(
bedrock_addr,
temp_indexer_dir.path().to_owned(),
initial_data,
)
.context("Failed to create Indexer config")?;
let config_json = serde_json::to_vec(&indexer_config)?;
let config_path = temp_indexer_dir.path().join("indexer_config.json");
let mut file = File::create(config_path.as_path())?;
file.write_all(&config_json)?;
file.flush()?;
let res =
// SAFETY: lib function ensures validity of value.
unsafe { start_indexer(CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) };
if res.error.is_error() {
anyhow::bail!("Indexer FFI error {:?}", res.error);
}
Ok((
// SAFETY: lib function ensures validity of value.
unsafe { std::ptr::read(res.value) },
temp_indexer_dir,
))
}
+296
View File
@@ -0,0 +1,296 @@
use std::sync::Arc;
use anyhow::{Context as _, Result};
use futures::FutureExt as _;
use indexer_ffi::IndexerServiceFFI;
use indexer_service_rpc::RpcClient as _;
use log::{debug, error};
use nssa::AccountId;
use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _};
use sequencer_service::SequencerHandle;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use tempfile::TempDir;
use testcontainers::compose::DockerCompose;
use wallet::WalletCore;
use crate::{
BEDROCK_SERVICE_WITH_OPEN_PORT, LOGGER, TestContextBuilder, config,
setup::{setup_bedrock_node, setup_indexer_ffi, setup_sequencer, setup_wallet},
};
/// Test context which sets up a sequencer, indexer through ffi and a wallet for integration tests.
///
/// It's memory and logically safe to create multiple instances of this struct in parallel tests,
/// as each instance uses its own temporary directories for sequencer and wallet data.
// NOTE: Order of fields is important for proper drop order.
pub struct TestContextFFI {
sequencer_client: SequencerClient,
indexer_client: IndexerClient,
wallet: WalletCore,
wallet_password: String,
/// Optional to move out value in Drop.
sequencer_handle: Option<SequencerHandle>,
bedrock_compose: DockerCompose,
_temp_indexer_dir: TempDir,
_temp_sequencer_dir: TempDir,
_temp_wallet_dir: TempDir,
}
#[expect(
clippy::multiple_inherent_impl,
reason = "It is more natural to have this implementation here"
)]
impl TestContextBuilder {
pub fn build_ffi(
self,
runtime: &Arc<tokio::runtime::Runtime>,
) -> Result<(TestContextFFI, IndexerServiceFFI)> {
TestContextFFI::new_configured(
self.sequencer_partial_config.unwrap_or_default(),
&self.initial_data.unwrap_or_else(|| {
config::InitialData::with_two_public_and_two_private_initialized_accounts()
}),
runtime,
)
}
}
impl TestContextFFI {
/// Create new test context.
pub fn new(runtime: &Arc<tokio::runtime::Runtime>) -> Result<(Self, IndexerServiceFFI)> {
Self::builder().build_ffi(runtime)
}
#[must_use]
pub const fn builder() -> TestContextBuilder {
TestContextBuilder::new()
}
fn new_configured(
sequencer_partial_config: config::SequencerPartialConfig,
initial_data: &config::InitialData,
runtime: &Arc<tokio::runtime::Runtime>,
) -> Result<(Self, IndexerServiceFFI)> {
// Ensure logger is initialized only once
*LOGGER;
debug!("Test context setup");
let (bedrock_compose, bedrock_addr) = runtime.block_on(setup_bedrock_node())?;
let (indexer_ffi, temp_indexer_dir) =
setup_indexer_ffi(bedrock_addr, initial_data).context("Failed to setup Indexer")?;
let (sequencer_handle, temp_sequencer_dir) = runtime
.block_on(setup_sequencer(
sequencer_partial_config,
bedrock_addr,
// SAFETY: addr is valid if indexer_ffi is valid.
unsafe { indexer_ffi.addr() },
initial_data,
))
.context("Failed to setup Sequencer")?;
let (wallet, temp_wallet_dir, wallet_password) = runtime
.block_on(setup_wallet(sequencer_handle.addr(), initial_data))
.context("Failed to setup wallet")?;
let sequencer_url = config::addr_to_url(config::UrlProtocol::Http, sequencer_handle.addr())
.context("Failed to convert sequencer addr to URL")?;
let indexer_url = config::addr_to_url(
config::UrlProtocol::Ws,
// SAFETY: addr is valid if indexer_ffi is valid.
unsafe { indexer_ffi.addr() },
)
.context("Failed to convert indexer addr to URL")?;
let sequencer_client = SequencerClientBuilder::default()
.build(sequencer_url)
.context("Failed to create sequencer client")?;
let indexer_client = runtime
.block_on(IndexerClient::new(&indexer_url))
.context("Failed to create indexer client")?;
Ok((
Self {
sequencer_client,
indexer_client,
wallet,
wallet_password,
bedrock_compose,
sequencer_handle: Some(sequencer_handle),
_temp_indexer_dir: temp_indexer_dir,
_temp_sequencer_dir: temp_sequencer_dir,
_temp_wallet_dir: temp_wallet_dir,
},
indexer_ffi,
))
}
/// Get reference to the wallet.
#[must_use]
pub const fn wallet(&self) -> &WalletCore {
&self.wallet
}
#[must_use]
pub fn wallet_password(&self) -> &str {
&self.wallet_password
}
/// Get mutable reference to the wallet.
pub const fn wallet_mut(&mut self) -> &mut WalletCore {
&mut self.wallet
}
/// Get reference to the sequencer client.
#[must_use]
pub const fn sequencer_client(&self) -> &SequencerClient {
&self.sequencer_client
}
/// Get reference to the indexer client.
#[must_use]
pub const fn indexer_client(&self) -> &IndexerClient {
&self.indexer_client
}
/// Get existing public account IDs in the wallet.
#[must_use]
pub fn existing_public_accounts(&self) -> Vec<AccountId> {
self.wallet
.storage()
.user_data
.public_account_ids()
.collect()
}
/// Get existing private account IDs in the wallet.
#[must_use]
pub fn existing_private_accounts(&self) -> Vec<AccountId> {
self.wallet
.storage()
.user_data
.private_account_ids()
.collect()
}
pub fn get_last_block_sequencer(&self, runtime: &Arc<tokio::runtime::Runtime>) -> Result<u64> {
Ok(runtime.block_on(self.sequencer_client.get_last_block_id())?)
}
pub fn get_last_block_indexer(&self, runtime: &Arc<tokio::runtime::Runtime>) -> Result<u64> {
Ok(runtime.block_on(self.indexer_client.get_last_finalized_block_id())?)
}
}
impl Drop for TestContextFFI {
fn drop(&mut self) {
let Self {
sequencer_handle,
bedrock_compose,
_temp_indexer_dir: _,
_temp_sequencer_dir: _,
_temp_wallet_dir: _,
sequencer_client: _,
indexer_client: _,
wallet: _,
wallet_password: _,
} = self;
let sequencer_handle = sequencer_handle
.take()
.expect("Sequencer handle should be present in TestContext drop");
if !sequencer_handle.is_healthy() {
let Err(err) = sequencer_handle
.failed()
.now_or_never()
.expect("Sequencer handle should not be running");
error!(
"Sequencer handle has unexpectedly stopped before TestContext drop with error: {err:#}"
);
}
let container = bedrock_compose
.service(BEDROCK_SERVICE_WITH_OPEN_PORT)
.unwrap_or_else(|| {
panic!("Failed to get Bedrock service container `{BEDROCK_SERVICE_WITH_OPEN_PORT}`")
});
let output = std::process::Command::new("docker")
.args(["inspect", "-f", "{{.State.Running}}", container.id()])
.output()
.expect("Failed to execute docker inspect command to check if Bedrock container is still running");
let stdout = String::from_utf8(output.stdout)
.expect("Failed to parse docker inspect output as String");
if stdout.trim() != "true" {
error!(
"Bedrock container `{}` is not running during TestContext drop, docker inspect output: {stdout}",
container.id()
);
}
}
}
/// A test context with ffi to be used in normal #[test] tests.
pub struct BlockingTestContextFFI {
ctx: Option<TestContextFFI>,
runtime: Arc<tokio::runtime::Runtime>,
indexer_ffi: IndexerServiceFFI,
}
impl BlockingTestContextFFI {
pub fn new() -> Result<Self> {
let runtime = tokio::runtime::Runtime::new().unwrap();
let runtime_wrapped = Arc::new(runtime);
let (ctx, indexer_ffi) = TestContextFFI::new(&runtime_wrapped)?;
Ok(Self {
ctx: Some(ctx),
runtime: runtime_wrapped,
indexer_ffi,
})
}
#[must_use]
pub const fn ctx(&self) -> &TestContextFFI {
self.ctx.as_ref().expect("TestContext is set")
}
#[must_use]
pub const fn ctx_mut(&mut self) -> &mut TestContextFFI {
self.ctx.as_mut().expect("TestContext is set")
}
#[must_use]
pub const fn runtime(&self) -> &Arc<tokio::runtime::Runtime> {
&self.runtime
}
#[must_use]
pub fn runtime_clone(&self) -> Arc<tokio::runtime::Runtime> {
Arc::<tokio::runtime::Runtime>::clone(&self.runtime)
}
}
impl Drop for BlockingTestContextFFI {
fn drop(&mut self) {
let Self {
ctx,
runtime,
indexer_ffi,
} = self;
// Ensure async cleanup of TestContext by blocking on its drop in the runtime.
runtime.block_on(async {
if let Some(ctx) = ctx.take() {
drop(ctx);
}
});
let indexer_handle =
// SAFETY: lib function ensures validity of value.
unsafe { indexer_ffi.handle() };
if !indexer_handle.is_healthy() {
error!("Indexer handle has unexpectedly stopped before TestContext drop");
}
}
}
+3 -3
View File
@@ -7,6 +7,7 @@ use anyhow::Result;
use integration_tests::TestContext;
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command,
@@ -21,8 +22,7 @@ async fn get_existing_account() -> Result<()> {
let account = ctx
.sequencer_client()
.get_account(ctx.existing_public_accounts()[0])
.await?
.account;
.await?;
assert_eq!(
account.program_owner,
@@ -30,7 +30,7 @@ async fn get_existing_account() -> Result<()> {
);
assert_eq!(account.balance, 10000);
assert!(account.data.is_empty());
assert_eq!(account.nonce, 0);
assert_eq!(account.nonce.0, 0);
info!("Successfully retrieved account with correct details");
+245 -66
View File
@@ -9,6 +9,7 @@ use std::time::Duration;
use anyhow::Result;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
use log::info;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
@@ -112,9 +113,12 @@ async fn amm_public() -> Result<()> {
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(definition_account_id_1),
supply_account_id: format_public_account_id(supply_account_id_1),
definition_account_id: Some(format_public_account_id(definition_account_id_1)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_1)),
supply_account_label: None,
name: "A NAM1".to_owned(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -123,8 +127,10 @@ async fn amm_public() -> Result<()> {
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1`
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(supply_account_id_1),
from: Some(format_public_account_id(supply_account_id_1)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id_1)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 7,
@@ -136,9 +142,12 @@ async fn amm_public() -> Result<()> {
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(definition_account_id_2),
supply_account_id: format_public_account_id(supply_account_id_2),
definition_account_id: Some(format_public_account_id(definition_account_id_2)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_2)),
supply_account_label: None,
name: "A NAM2".to_owned(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -147,8 +156,10 @@ async fn amm_public() -> Result<()> {
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2`
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(supply_account_id_2),
from: Some(format_public_account_id(supply_account_id_2)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id_2)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 7,
@@ -180,9 +191,12 @@ async fn amm_public() -> Result<()> {
// Send creation tx
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: format_public_account_id(recipient_account_id_1),
user_holding_b: format_public_account_id(recipient_account_id_2),
user_holding_lp: format_public_account_id(user_holding_lp),
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_lp: Some(format_public_account_id(user_holding_lp)),
user_holding_lp_label: None,
balance_a: 3,
balance_b: 3,
};
@@ -194,20 +208,14 @@ async fn amm_public() -> Result<()> {
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1)
.await?
.account;
.await?;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2)
.await?
.account;
.await?;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp)
.await?
.account;
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
@@ -228,9 +236,11 @@ async fn amm_public() -> Result<()> {
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::Swap {
user_holding_a: format_public_account_id(recipient_account_id_1),
user_holding_b: format_public_account_id(recipient_account_id_2),
let subcommand = AmmProgramAgnosticSubcommand::SwapExactInput {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_1.to_string(),
@@ -243,20 +253,14 @@ async fn amm_public() -> Result<()> {
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1)
.await?
.account;
.await?;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2)
.await?
.account;
.await?;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp)
.await?
.account;
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
@@ -277,9 +281,11 @@ async fn amm_public() -> Result<()> {
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::Swap {
user_holding_a: format_public_account_id(recipient_account_id_1),
user_holding_b: format_public_account_id(recipient_account_id_2),
let subcommand = AmmProgramAgnosticSubcommand::SwapExactInput {
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_2.to_string(),
@@ -292,20 +298,14 @@ async fn amm_public() -> Result<()> {
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1)
.await?
.account;
.await?;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2)
.await?
.account;
.await?;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp)
.await?
.account;
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
@@ -327,9 +327,12 @@ async fn amm_public() -> Result<()> {
// Add liquidity
let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity {
user_holding_a: format_public_account_id(recipient_account_id_1),
user_holding_b: format_public_account_id(recipient_account_id_2),
user_holding_lp: format_public_account_id(user_holding_lp),
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_lp: Some(format_public_account_id(user_holding_lp)),
user_holding_lp_label: None,
min_amount_lp: 1,
max_amount_a: 2,
max_amount_b: 2,
@@ -342,20 +345,14 @@ async fn amm_public() -> Result<()> {
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1)
.await?
.account;
.await?;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2)
.await?
.account;
.await?;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp)
.await?
.account;
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
@@ -377,9 +374,12 @@ async fn amm_public() -> Result<()> {
// Remove liquidity
let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity {
user_holding_a: format_public_account_id(recipient_account_id_1),
user_holding_b: format_public_account_id(recipient_account_id_2),
user_holding_lp: format_public_account_id(user_holding_lp),
user_holding_a: Some(format_public_account_id(recipient_account_id_1)),
user_holding_a_label: None,
user_holding_b: Some(format_public_account_id(recipient_account_id_2)),
user_holding_b_label: None,
user_holding_lp: Some(format_public_account_id(user_holding_lp)),
user_holding_lp_label: None,
balance_lp: 2,
min_amount_a: 1,
min_amount_b: 1,
@@ -392,20 +392,14 @@ async fn amm_public() -> Result<()> {
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1)
.await?
.account;
.await?;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2)
.await?
.account;
.await?;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp)
.await?
.account;
let user_holding_lp_acc = ctx.sequencer_client().get_account(user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
@@ -426,3 +420,188 @@ async fn amm_public() -> Result<()> {
Ok(())
}
#[test]
async fn amm_new_pool_using_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token 1 accounts
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id_1,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id_1,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create holding_a with a label
let holding_a_label = "amm-holding-a-label".to_owned();
let SubcommandReturnValue::RegisterAccount {
account_id: holding_a_id,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(holding_a_label.clone()),
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token 2 accounts
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id_2,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id_2,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create holding_b with a label
let holding_b_label = "amm-holding-b-label".to_owned();
let SubcommandReturnValue::RegisterAccount {
account_id: holding_b_id,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(holding_b_label.clone()),
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create holding_lp with a label
let holding_lp_label = "amm-holding-lp-label".to_owned();
let SubcommandReturnValue::RegisterAccount {
account_id: holding_lp_id,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(holding_lp_label.clone()),
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token 1 and distribute to holding_a
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id_1)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_1)),
supply_account_label: None,
name: "TOKEN1".to_owned(),
total_supply: 10,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id_1)),
from_label: None,
to: Some(format_public_account_id(holding_a_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 5,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create token 2 and distribute to holding_b
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id_2)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id_2)),
supply_account_label: None,
name: "TOKEN2".to_owned(),
total_supply: 10,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id_2)),
from_label: None,
to: Some(format_public_account_id(holding_b_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 5,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create AMM pool using account labels instead of IDs
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: None,
user_holding_a_label: Some(holding_a_label),
user_holding_b: None,
user_holding_b_label: Some(holding_b_label),
user_holding_lp: None,
user_holding_lp_label: Some(holding_lp_label),
balance_a: 3,
balance_b: 3,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let holding_lp_acc = ctx.sequencer_client().get_account(holding_lp_id).await?;
// LP balance should be 3 (geometric mean of 3, 3)
assert_eq!(
u128::from_le_bytes(holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("Successfully created AMM pool using account labels");
Ok(())
}
+674
View File
@@ -0,0 +1,674 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::{Context as _, Result};
use ata_core::{compute_ata_seed, get_associated_token_account_id};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
};
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::{ata::AtaSubcommand, token::TokenProgramAgnosticSubcommand},
};
/// Create a public account and return its ID.
async fn new_public_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
anyhow::bail!("Expected RegisterAccount return value");
};
Ok(account_id)
}
/// Create a private account and return its ID.
async fn new_private_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: None,
label: None,
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
anyhow::bail!("Expected RegisterAccount return value");
};
Ok(account_id)
}
#[test]
async fn create_ata_initializes_holding_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_public_account(&mut ctx).await?;
let supply_account_id = new_public_account(&mut ctx).await?;
let owner_account_id = new_public_account(&mut ctx).await?;
// Create a fungible token
let total_supply = 100_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "TEST".to_owned(),
total_supply,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create the ATA for owner + definition
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Derive expected ATA address and check on-chain state
let ata_program_id = Program::ata().id();
let ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(owner_account_id, definition_account_id),
);
let ata_acc = ctx
.sequencer_client()
.get_account(ata_id)
.await
.context("ATA account not found")?;
assert_eq!(ata_acc.program_owner, Program::token().id());
let holding = TokenHolding::try_from(&ata_acc.data)?;
assert_eq!(
holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: 0,
}
);
Ok(())
}
#[test]
async fn create_ata_is_idempotent() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_public_account(&mut ctx).await?;
let supply_account_id = new_public_account(&mut ctx).await?;
let owner_account_id = new_public_account(&mut ctx).await?;
// Create a fungible token
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "TEST".to_owned(),
total_supply: 100,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create the ATA once
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create the ATA a second time — must succeed (idempotent)
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// State must be unchanged
let ata_program_id = Program::ata().id();
let ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(owner_account_id, definition_account_id),
);
let ata_acc = ctx
.sequencer_client()
.get_account(ata_id)
.await
.context("ATA account not found")?;
assert_eq!(ata_acc.program_owner, Program::token().id());
let holding = TokenHolding::try_from(&ata_acc.data)?;
assert_eq!(
holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: 0,
}
);
Ok(())
}
#[test]
async fn transfer_and_burn_via_ata() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_public_account(&mut ctx).await?;
let supply_account_id = new_public_account(&mut ctx).await?;
let sender_account_id = new_public_account(&mut ctx).await?;
let recipient_account_id = new_public_account(&mut ctx).await?;
let total_supply = 1000_u128;
// Create a fungible token, supply goes to supply_account_id
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "TEST".to_owned(),
total_supply,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Derive ATA addresses
let ata_program_id = Program::ata().id();
let sender_ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(sender_account_id, definition_account_id),
);
let recipient_ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(recipient_account_id, definition_account_id),
);
// Create ATAs for sender and recipient
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(recipient_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Fund sender's ATA from the supply account (direct token transfer)
let fund_amount = 200_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(sender_ata_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: fund_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Transfer from sender's ATA to recipient's ATA via the ATA program
let transfer_amount = 50_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Send {
from: format_public_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
to: recipient_ata_id.to_string(),
amount: transfer_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify sender ATA balance decreased
let sender_ata_acc = ctx.sequencer_client().get_account(sender_ata_id).await?;
let sender_holding = TokenHolding::try_from(&sender_ata_acc.data)?;
assert_eq!(
sender_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: fund_amount - transfer_amount,
}
);
// Verify recipient ATA balance increased
let recipient_ata_acc = ctx.sequencer_client().get_account(recipient_ata_id).await?;
let recipient_holding = TokenHolding::try_from(&recipient_ata_acc.data)?;
assert_eq!(
recipient_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount,
}
);
// Burn from sender's ATA
let burn_amount = 30_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Burn {
holder: format_public_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
amount: burn_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify sender ATA balance after burn
let sender_ata_acc = ctx.sequencer_client().get_account(sender_ata_id).await?;
let sender_holding = TokenHolding::try_from(&sender_ata_acc.data)?;
assert_eq!(
sender_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: fund_amount - transfer_amount - burn_amount,
}
);
// Verify the token definition total_supply decreased by burn_amount
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name: "TEST".to_owned(),
total_supply: total_supply - burn_amount,
metadata_id: None,
}
);
Ok(())
}
#[test]
async fn create_ata_with_private_owner() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_public_account(&mut ctx).await?;
let supply_account_id = new_public_account(&mut ctx).await?;
let owner_account_id = new_private_account(&mut ctx).await?;
// Create a fungible token
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "TEST".to_owned(),
total_supply: 100,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create the ATA for the private owner + definition
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_private_account_id(owner_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Derive expected ATA address and check on-chain state
let ata_program_id = Program::ata().id();
let ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(owner_account_id, definition_account_id),
);
let ata_acc = ctx
.sequencer_client()
.get_account(ata_id)
.await
.context("ATA account not found")?;
assert_eq!(ata_acc.program_owner, Program::token().id());
let holding = TokenHolding::try_from(&ata_acc.data)?;
assert_eq!(
holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: 0,
}
);
// Verify the private owner's commitment is in state
let commitment = ctx
.wallet()
.get_private_account_commitment(owner_account_id)
.context("Private owner commitment not found")?;
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
Ok(())
}
#[test]
async fn transfer_via_ata_private_owner() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_public_account(&mut ctx).await?;
let supply_account_id = new_public_account(&mut ctx).await?;
let sender_account_id = new_private_account(&mut ctx).await?;
let recipient_account_id = new_public_account(&mut ctx).await?;
let total_supply = 1000_u128;
// Create a fungible token
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "TEST".to_owned(),
total_supply,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Derive ATA addresses
let ata_program_id = Program::ata().id();
let sender_ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(sender_account_id, definition_account_id),
);
let recipient_ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(recipient_account_id, definition_account_id),
);
// Create ATAs for sender (private owner) and recipient (public owner)
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_private_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_public_account_id(recipient_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Fund sender's ATA from the supply account (direct token transfer)
let fund_amount = 200_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(sender_ata_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: fund_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Transfer from sender's ATA (private owner) to recipient's ATA
let transfer_amount = 50_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Send {
from: format_private_account_id(sender_account_id),
token_definition: definition_account_id.to_string(),
to: recipient_ata_id.to_string(),
amount: transfer_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify sender ATA balance decreased
let sender_ata_acc = ctx.sequencer_client().get_account(sender_ata_id).await?;
let sender_holding = TokenHolding::try_from(&sender_ata_acc.data)?;
assert_eq!(
sender_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: fund_amount - transfer_amount,
}
);
// Verify recipient ATA balance increased
let recipient_ata_acc = ctx.sequencer_client().get_account(recipient_ata_id).await?;
let recipient_holding = TokenHolding::try_from(&recipient_ata_acc.data)?;
assert_eq!(
recipient_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount,
}
);
// Verify the private sender's commitment is in state
let commitment = ctx
.wallet()
.get_private_account_commitment(sender_account_id)
.context("Private sender commitment not found")?;
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
Ok(())
}
#[test]
async fn burn_via_ata_private_owner() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_public_account(&mut ctx).await?;
let supply_account_id = new_public_account(&mut ctx).await?;
let holder_account_id = new_private_account(&mut ctx).await?;
let total_supply = 500_u128;
// Create a fungible token
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "TEST".to_owned(),
total_supply,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Derive holder's ATA address
let ata_program_id = Program::ata().id();
let holder_ata_id = get_associated_token_account_id(
&ata_program_id,
&compute_ata_seed(holder_account_id, definition_account_id),
);
// Create ATA for the private holder
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: format_private_account_id(holder_account_id),
token_definition: definition_account_id.to_string(),
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Fund holder's ATA from the supply account
let fund_amount = 300_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Token(TokenProgramAgnosticSubcommand::Send {
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(holder_ata_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: fund_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Burn from holder's ATA (private owner)
let burn_amount = 100_u128;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Burn {
holder: format_private_account_id(holder_account_id),
token_definition: definition_account_id.to_string(),
amount: burn_amount,
}),
)
.await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify holder ATA balance after burn
let holder_ata_acc = ctx.sequencer_client().get_account(holder_ata_id).await?;
let holder_holding = TokenHolding::try_from(&holder_ata_acc.data)?;
assert_eq!(
holder_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: fund_amount - burn_amount,
}
);
// Verify the token definition total_supply decreased by burn_amount
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name: "TEST".to_owned(),
total_supply: total_supply - burn_amount,
metadata_id: None,
}
);
// Verify the private holder's commitment is in state
let commitment = ctx
.wallet()
.get_private_account_commitment(holder_account_id)
.context("Private holder commitment not found")?;
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
Ok(())
}
+126 -13
View File
@@ -8,6 +8,7 @@ use integration_tests::{
use log::info;
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
@@ -23,8 +24,10 @@ async fn private_transfer_to_owned_account() -> Result<()> {
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -62,8 +65,10 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
let to_vpk = Secp256k1Point::from_scalar(to_npk.0);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: None,
to_label: None,
to_npk: Some(to_npk_string),
to_vpk: Some(hex::encode(to_vpk.0)),
amount: 100,
@@ -110,8 +115,10 @@ async fn deshielded_transfer_to_public_account() -> Result<()> {
assert_eq!(from_acc.balance, 10000);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_public_account_id(to)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -135,7 +142,7 @@ async fn deshielded_transfer_to_public_account() -> Result<()> {
let acc_2_balance = ctx.sequencer_client().get_account_balance(to).await?;
assert_eq!(from_acc.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
assert_eq!(acc_2_balance, 20100);
info!("Successfully deshielded transfer to public account");
@@ -173,9 +180,11 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
// Send to this account using claiming path (using npk and vpk instead of account ID)
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_label: None,
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
amount: 100,
});
@@ -221,8 +230,10 @@ async fn shielded_transfer_to_owned_private_account() -> Result<()> {
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(from),
from: Some(format_public_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -245,7 +256,7 @@ async fn shielded_transfer_to_owned_private_account() -> Result<()> {
let acc_from_balance = ctx.sequencer_client().get_account_balance(from).await?;
assert_eq!(acc_from_balance.balance, 9900);
assert_eq!(acc_from_balance, 9900);
assert_eq!(acc_to.balance, 20100);
info!("Successfully shielded transfer to owned private account");
@@ -263,8 +274,10 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
let from: AccountId = ctx.existing_public_accounts()[0];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(from),
from: Some(format_public_account_id(from)),
from_label: None,
to: None,
to_label: None,
to_npk: Some(to_npk_string),
to_vpk: Some(hex::encode(to_vpk.0)),
amount: 100,
@@ -290,7 +303,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
.await
);
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_1_balance, 9900);
info!("Successfully shielded transfer to foreign account");
@@ -333,9 +346,11 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
// Send transfer using nullifier and viewing public keys
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_label: None,
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
amount: 100,
});
@@ -382,7 +397,8 @@ async fn initialize_private_account() -> Result<()> {
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: format_private_account_id(account_id),
account_id: Some(format_private_account_id(account_id)),
account_label: None,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@@ -414,3 +430,100 @@ async fn initialize_private_account() -> Result<()> {
Ok(())
}
#[test]
async fn private_transfer_using_from_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ctx.existing_private_accounts()[0];
let to: AccountId = ctx.existing_private_accounts()[1];
// Assign a label to the sender account
let label = "private-sender-label".to_owned();
let command = Command::Account(AccountSubcommand::Label {
account_id: Some(format_private_account_id(from)),
account_label: None,
label: label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send using the label instead of account ID
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(label),
to: Some(format_private_account_id(to)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately using from_label");
Ok(())
}
#[test]
async fn initialize_private_account_using_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create a new private account with a label
let label = "init-private-label".to_owned();
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: None,
label: Some(label.clone()),
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Initialize using the label instead of account ID
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: None,
account_label: Some(label),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(account_id)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let account = ctx
.wallet()
.get_account_private(account_id)
.context("Failed to get private account")?;
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
info!("Successfully initialized private account using label");
Ok(())
}
+124 -22
View File
@@ -4,6 +4,7 @@ use anyhow::Result;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
@@ -16,8 +17,10 @@ async fn successful_transfer_to_existing_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ctx.existing_public_accounts()[0]),
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -41,8 +44,8 @@ async fn successful_transfer_to_existing_account() -> Result<()> {
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
Ok(())
}
@@ -72,8 +75,10 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
.expect("Failed to find newly created account in the wallet storage");
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ctx.existing_public_accounts()[0]),
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(new_persistent_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -97,8 +102,8 @@ pub async fn successful_transfer_to_new_account() -> Result<()> {
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 100);
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 100);
Ok(())
}
@@ -108,8 +113,10 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ctx.existing_public_accounts()[0]),
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 1_000_000,
@@ -134,8 +141,8 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 10000);
assert_eq!(acc_2_balance.balance, 20000);
assert_eq!(acc_1_balance, 10000);
assert_eq!(acc_2_balance, 20000);
Ok(())
}
@@ -146,8 +153,10 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
// First transfer
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ctx.existing_public_accounts()[0]),
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -171,15 +180,17 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("First TX Success!");
// Second transfer
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ctx.existing_public_accounts()[0]),
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -203,8 +214,8 @@ async fn two_consecutive_successful_transfers() -> Result<()> {
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9800);
assert_eq!(acc_2_balance.balance, 20200);
assert_eq!(acc_1_balance, 9800);
assert_eq!(acc_2_balance, 20200);
info!("Second TX Success!");
@@ -225,26 +236,117 @@ async fn initialize_public_account() -> Result<()> {
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: format_public_account_id(account_id),
account_id: Some(format_public_account_id(account_id)),
account_label: None,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Checking correct execution");
let account = ctx
.sequencer_client()
.get_account(account_id)
.await?
.account;
let account = ctx.sequencer_client().get_account(account_id).await?;
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(account.balance, 0);
assert_eq!(account.nonce, 1);
assert_eq!(account.nonce.0, 1);
assert!(account.data.is_empty());
info!("Successfully initialized public account");
Ok(())
}
#[test]
async fn successful_transfer_using_from_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign a label to the sender account
let label = "sender-label".to_owned();
let command = Command::Account(AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
account_label: None,
label: label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send using the label instead of account ID
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(label),
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[0])
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[1])
.await?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Successfully transferred using from_label");
Ok(())
}
#[test]
async fn successful_transfer_using_to_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign a label to the receiver account
let label = "receiver-label".to_owned();
let command = Command::Account(AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
account_label: None,
label: label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send using the label for the recipient
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: None,
to_label: Some(label),
to_npk: None,
to_vpk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[0])
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[1])
.await?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Successfully transferred using to_label");
Ok(())
}
+27 -15
View File
@@ -8,11 +8,12 @@ use std::time::Duration;
use anyhow::Result;
use bytesize::ByteSize;
use common::{block::HashableBlockData, transaction::NSSATransaction};
use common::transaction::NSSATransaction;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, config::SequencerPartialConfig,
};
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
#[test]
@@ -36,7 +37,10 @@ async fn reject_oversized_transaction() -> Result<()> {
let tx = nssa::ProgramDeploymentTransaction::new(message);
// Try to submit the transaction and expect an error
let result = ctx.sequencer_client().send_tx_program(tx).await;
let result = ctx
.sequencer_client()
.send_transaction(NSSATransaction::ProgramDeployment(tx))
.await;
assert!(
result.is_err(),
@@ -74,7 +78,10 @@ async fn accept_transaction_within_limit() -> Result<()> {
let tx = nssa::ProgramDeploymentTransaction::new(message);
// This should succeed
let result = ctx.sequencer_client().send_tx_program(tx).await;
let result = ctx
.sequencer_client()
.send_transaction(NSSATransaction::ProgramDeployment(tx))
.await;
assert!(
result.is_ok(),
@@ -112,33 +119,38 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> {
let burner_id = Program::new(burner_bytecode.clone())?.id();
let chain_caller_id = Program::new(chain_caller_bytecode.clone())?.id();
let initial_block_height = ctx.sequencer_client().get_last_block().await?.last_block;
let initial_block_height = ctx.sequencer_client().get_last_block_id().await?;
// Submit both program deployments
ctx.sequencer_client()
.send_tx_program(nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(burner_bytecode),
.send_transaction(NSSATransaction::ProgramDeployment(
nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(burner_bytecode),
),
))
.await?;
ctx.sequencer_client()
.send_tx_program(nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(chain_caller_bytecode),
.send_transaction(NSSATransaction::ProgramDeployment(
nssa::ProgramDeploymentTransaction::new(
nssa::program_deployment_transaction::Message::new(chain_caller_bytecode),
),
))
.await?;
// Wait for first block
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let block1_response = ctx
let block1 = ctx
.sequencer_client()
.get_block(initial_block_height + 1)
.await?;
let block1: HashableBlockData = borsh::from_slice(&block1_response.block)?;
.await?
.unwrap();
// Check which program is in block 1
let get_program_ids = |block: &HashableBlockData| -> Vec<nssa::ProgramId> {
let get_program_ids = |block: &common::block::Block| -> Vec<nssa::ProgramId> {
block
.body
.transactions
.iter()
.filter_map(|tx| {
@@ -168,11 +180,11 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> {
// Wait for second block
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let block2_response = ctx
let block2 = ctx
.sequencer_client()
.get_block(initial_block_height + 2)
.await?;
let block2: HashableBlockData = borsh::from_slice(&block2_response.block)?;
.await?
.unwrap();
let block2_program_ids = get_program_ids(&block2);
// The other program should be in block 2
+180 -54
View File
@@ -1,42 +1,67 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use std::time::Duration;
use anyhow::Result;
use anyhow::{Context as _, Result};
use indexer_service_rpc::RpcClient as _;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
};
use log::info;
use tokio::test;
use nssa::AccountId;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
/// Timeout in milliseconds to reliably await for block finalization.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 600_000;
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
#[test]
/// Poll the indexer until its last finalized block id reaches the sequencer's
/// current last block id (and at least the genesis block has been advanced past),
/// or until [`L2_TO_L1_TIMEOUT_MILLIS`] elapses. Returns the last indexer block
/// id observed.
async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> u64 {
let timeout = Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS);
let mut last_ind: u64 = 1;
let inner = async {
loop {
let seq = sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client())
.await
.unwrap_or(0);
let ind = ctx
.indexer_client()
.get_last_finalized_block_id()
.await
.unwrap_or(1);
last_ind = ind;
if ind >= seq && ind > 1 {
info!("Indexer caught up: seq={seq}, ind={ind}");
return ind;
}
tokio::time::sleep(Duration::from_secs(2)).await;
}
};
tokio::time::timeout(timeout, inner)
.await
.unwrap_or_else(|_| {
info!("Indexer catch-up timed out: ind={last_ind}");
last_ind
})
}
#[tokio::test]
async fn indexer_test_run() -> Result<()> {
let ctx = TestContext::new().await?;
// RUN OBSERVATION
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await;
let last_block_seq = ctx
.sequencer_client()
.get_last_block()
.await
.unwrap()
.last_block;
let last_block_seq =
sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?;
info!("Last block on seq now is {last_block_seq}");
let last_block_indexer = ctx
.indexer_client()
.get_last_finalized_block_id()
.await
.unwrap();
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 1);
@@ -44,19 +69,12 @@ async fn indexer_test_run() -> Result<()> {
Ok(())
}
#[test]
#[tokio::test]
async fn indexer_block_batching() -> Result<()> {
let ctx = TestContext::new().await?;
// WAIT
info!("Waiting for indexer to parse blocks");
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
let last_block_indexer = ctx
.indexer_client()
.get_last_finalized_block_id()
.await
.unwrap();
let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await;
info!("Last block on ind now is {last_block_indexer}");
@@ -82,13 +100,15 @@ async fn indexer_block_batching() -> Result<()> {
Ok(())
}
#[test]
#[tokio::test]
async fn indexer_state_consistency() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ctx.existing_public_accounts()[0]),
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -100,24 +120,57 @@ async fn indexer_state_consistency() -> Result<()> {
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[0])
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[1])
.await?;
let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
let from: AccountId = ctx.existing_private_accounts()[0];
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately to owned account");
// WAIT
info!("Waiting for indexer to parse blocks");
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
wait_for_indexer_to_catch_up(&ctx).await;
let acc1_ind_state = ctx
.indexer_client()
@@ -131,16 +184,16 @@ async fn indexer_state_consistency() -> Result<()> {
.unwrap();
info!("Checking correct state transition");
let acc1_seq_state = ctx
.sequencer_client()
.get_account(ctx.existing_public_accounts()[0])
.await?
.account;
let acc2_seq_state = ctx
.sequencer_client()
.get_account(ctx.existing_public_accounts()[1])
.await?
.account;
let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc2_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
assert_eq!(acc2_ind_state, acc2_seq_state.into());
@@ -149,3 +202,76 @@ async fn indexer_state_consistency() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn indexer_state_consistency_with_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Assign labels to both accounts
let from_label = "idx-sender-label".to_owned();
let to_label_str = "idx-receiver-label".to_owned();
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
account_label: None,
label: from_label.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
account_label: None,
label: to_label_str.clone(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(from_label),
to: None,
to_label: Some(to_label_str),
to_npk: None,
to_vpk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
)
.await?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
wait_for_indexer_to_catch_up(&ctx).await;
let acc1_ind_state = ctx
.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into())
.await
.unwrap();
let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
info!("Indexer state is consistent after label-based transfer");
Ok(())
}
+284
View File
@@ -0,0 +1,284 @@
#![expect(
clippy::shadow_unrelated,
clippy::tests_outside_test_module,
reason = "We don't care about these in tests"
)]
use anyhow::{Context as _, Result};
use indexer_service_rpc::RpcClient as _;
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, format_private_account_id, format_public_account_id,
test_context_ffi::BlockingTestContextFFI, verify_commitment_is_in_state,
};
use log::info;
use nssa::AccountId;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
/// Maximum time to wait for the indexer to catch up to the sequencer.
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000;
#[test]
fn indexer_test_run_ffi() -> Result<()> {
let blocking_ctx = BlockingTestContextFFI::new()?;
let runtime_wrapped = blocking_ctx.runtime();
// RUN OBSERVATION
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
});
let last_block_indexer = blocking_ctx.ctx().get_last_block_indexer(runtime_wrapped)?;
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 1);
Ok(())
}
#[test]
fn indexer_ffi_block_batching() -> Result<()> {
let blocking_ctx = BlockingTestContextFFI::new()?;
let runtime_wrapped = blocking_ctx.runtime();
let ctx = blocking_ctx.ctx();
// WAIT
info!("Waiting for indexer to parse blocks");
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
});
let last_block_indexer = runtime_wrapped
.block_on(ctx.indexer_client().get_last_finalized_block_id())
.unwrap();
info!("Last block on ind now is {last_block_indexer}");
assert!(last_block_indexer > 1);
// Getting wide batch to fit all blocks (from latest backwards)
let mut block_batch = runtime_wrapped
.block_on(ctx.indexer_client().get_blocks(None, 100))
.unwrap();
// Reverse to check chain consistency from oldest to newest
block_batch.reverse();
// Checking chain consistency
let mut prev_block_hash = block_batch.first().unwrap().header.hash;
for block in &block_batch[1..] {
assert_eq!(block.header.prev_block_hash, prev_block_hash);
info!("Block {} chain-consistent", block.header.block_id);
prev_block_hash = block.header.hash;
}
Ok(())
}
#[test]
fn indexer_ffi_state_consistency() -> Result<()> {
let mut blocking_ctx = BlockingTestContextFFI::new()?;
let runtime_wrapped = blocking_ctx.runtime_clone();
let ctx = blocking_ctx.ctx_mut();
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
from_label: None,
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
});
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
))
.await;
});
info!("Checking correct balance move");
let acc_1_balance =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
))?;
let acc_2_balance =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
))?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
let from: AccountId = ctx.existing_private_accounts()[0];
let to: AccountId = ctx.existing_private_accounts()[1];
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
});
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
))
.await;
});
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
let commitment_check1 = runtime_wrapped.block_on(verify_commitment_is_in_state(
new_commitment1,
ctx.sequencer_client(),
));
assert!(commitment_check1);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
let commitment_check2 = runtime_wrapped.block_on(verify_commitment_is_in_state(
new_commitment2,
ctx.sequencer_client(),
));
assert!(commitment_check2);
info!("Successfully transferred privately to owned account");
// WAIT
info!("Waiting for indexer to parse blocks");
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
});
let acc1_ind_state = runtime_wrapped.block_on(
ctx.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into()),
)?;
let acc2_ind_state = runtime_wrapped.block_on(
ctx.indexer_client()
.get_account(ctx.existing_public_accounts()[1].into()),
)?;
info!("Checking correct state transition");
let acc1_seq_state =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
))?;
let acc2_seq_state =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
))?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
assert_eq!(acc2_ind_state, acc2_seq_state.into());
// ToDo: Check private state transition
Ok(())
}
#[test]
fn indexer_ffi_state_consistency_with_labels() -> Result<()> {
let mut blocking_ctx = BlockingTestContextFFI::new()?;
let runtime_wrapped = blocking_ctx.runtime_clone();
let ctx = blocking_ctx.ctx_mut();
// Assign labels to both accounts
let from_label = "idx-sender-label".to_owned();
let to_label_str = "idx-receiver-label".to_owned();
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
account_label: None,
label: from_label.clone(),
});
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?;
let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label {
account_id: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
account_label: None,
label: to_label_str.clone(),
});
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?;
// Send using labels instead of account IDs
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: None,
from_label: Some(from_label),
to: None,
to_label: Some(to_label_str),
to_npk: None,
to_vpk: None,
amount: 100,
});
runtime_wrapped.block_on(wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?;
info!("Waiting for next block creation");
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
))
.await;
});
let acc_1_balance =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
))?;
let acc_2_balance =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account_balance(
ctx.sequencer_client(),
ctx.existing_public_accounts()[1],
))?;
assert_eq!(acc_1_balance, 9900);
assert_eq!(acc_2_balance, 20100);
info!("Waiting for indexer to parse blocks");
runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
});
let acc1_ind_state = runtime_wrapped.block_on(
ctx.indexer_client()
.get_account(ctx.existing_public_accounts()[0].into()),
)?;
let acc1_seq_state =
runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
))?;
assert_eq!(acc1_ind_state, acc1_seq_state.into());
info!("Indexer state is consistent after label-based transfer");
Ok(())
}
+25 -10
View File
@@ -14,6 +14,7 @@ use integration_tests::{
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::{AccountId, program::Program};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
@@ -68,9 +69,11 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
// Send to this account using claiming path (using npk and vpk instead of account ID)
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_label: None,
to_npk: Some(hex::encode(to_keys.nullifier_public_key.0)),
to_vpk: Some(hex::encode(to_keys.viewing_public_key.0)),
amount: 100,
});
@@ -142,8 +145,10 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to first private account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to_account_id1)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 100,
@@ -152,8 +157,10 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to second private account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(from),
from: Some(format_private_account_id(from)),
from_label: None,
to: Some(format_private_account_id(to_account_id2)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 101,
@@ -190,8 +197,10 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to first public account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(from),
from: Some(format_public_account_id(from)),
from_label: None,
to: Some(format_public_account_id(to_account_id3)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 102,
@@ -200,8 +209,10 @@ async fn restore_keys_from_seed() -> Result<()> {
// Send to second public account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(from),
from: Some(format_public_account_id(from)),
from_label: None,
to: Some(format_public_account_id(to_account_id4)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 103,
@@ -263,8 +274,10 @@ async fn restore_keys_from_seed() -> Result<()> {
// Test that restored accounts can send transactions
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(to_account_id1),
from: Some(format_private_account_id(to_account_id1)),
from_label: None,
to: Some(format_private_account_id(to_account_id2)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 10,
@@ -272,8 +285,10 @@ async fn restore_keys_from_seed() -> Result<()> {
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(to_account_id3),
from: Some(format_public_account_id(to_account_id3)),
from_label: None,
to: Some(format_public_account_id(to_account_id4)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: 11,
@@ -305,8 +320,8 @@ async fn restore_keys_from_seed() -> Result<()> {
.get_account_balance(to_account_id4)
.await?;
assert_eq!(acc3.balance, 91); // 102 - 11
assert_eq!(acc4.balance, 114); // 103 + 11
assert_eq!(acc3, 91); // 102 - 11
assert_eq!(acc4, 114); // 103 + 11
info!("Successfully restored keys and verified transactions");
+26 -28
View File
@@ -13,6 +13,7 @@ use integration_tests::{
format_public_account_id, verify_commitment_is_in_state,
};
use log::info;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
@@ -46,13 +47,13 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()>
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: winner_account_id_formatted,
to: Some(winner_account_id_formatted),
to_label: None,
}),
)
.await;
@@ -70,8 +71,7 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()>
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
assert_eq!(pinata_balance_post, pinata_balance_pre);
@@ -102,13 +102,13 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: winner_account_id_formatted,
to: Some(winner_account_id_formatted),
to_label: None,
}),
)
.await;
@@ -126,8 +126,7 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
assert_eq!(pinata_balance_post, pinata_balance_pre);
@@ -140,14 +139,14 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> {
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: format_public_account_id(ctx.existing_public_accounts()[0]),
to: Some(format_public_account_id(ctx.existing_public_accounts()[0])),
to_label: None,
});
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@@ -158,14 +157,12 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> {
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
let winner_balance_post = ctx
.sequencer_client()
.get_account_balance(ctx.existing_public_accounts()[0])
.await?
.balance;
.await?;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
assert_eq!(winner_balance_post, 10000 + pinata_prize);
@@ -181,14 +178,16 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> {
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: format_private_account_id(ctx.existing_private_accounts()[0]),
to: Some(format_private_account_id(
ctx.existing_private_accounts()[0],
)),
to_label: None,
});
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else {
@@ -211,8 +210,7 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> {
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
@@ -247,7 +245,8 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
// Initialize account under auth transfer program
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: winner_account_id_formatted.clone(),
account_id: Some(winner_account_id_formatted.clone()),
account_label: None,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@@ -262,14 +261,14 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
// Claim pinata to the new private account
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: winner_account_id_formatted,
to: Some(winner_account_id_formatted),
to_label: None,
});
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@@ -285,8 +284,7 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.parse().unwrap())
.await?
.balance;
.await?;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
+33 -12
View File
@@ -6,13 +6,18 @@
use std::{path::PathBuf, time::Duration};
use anyhow::Result;
use common::transaction::NSSATransaction;
use integration_tests::{
NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
};
use log::info;
use nssa::{AccountId, program::Program};
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
use wallet::cli::Command;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
};
#[test]
async fn deploy_and_execute_program() -> Result<()> {
@@ -38,32 +43,48 @@ async fn deploy_and_execute_program() -> Result<()> {
// logic)
let bytecode = std::fs::read(binary_filepath)?;
let data_changer = Program::new(bytecode)?;
let account_id: AccountId = "11".repeat(16).parse()?;
let SubcommandReturnValue::RegisterAccount { account_id } = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?
else {
panic!("Expected RegisterAccount return value");
};
let nonces = ctx.wallet().get_accounts_nonces(vec![account_id]).await?;
let private_key = ctx
.wallet()
.get_account_public_signing_key(account_id)
.unwrap();
let message = nssa::public_transaction::Message::try_new(
data_changer.id(),
vec![account_id],
vec![],
nonces,
vec![0],
)?;
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]);
let transaction = nssa::PublicTransaction::new(message, witness_set);
let _response = ctx.sequencer_client().send_tx_public(transaction).await?;
let _response = ctx
.sequencer_client()
.send_transaction(NSSATransaction::Public(transaction))
.await?;
info!("Waiting for next block creation");
// Waiting for long time as it may take some time for such a big transaction to be included in a
// block
tokio::time::sleep(Duration::from_secs(2 * TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let post_state_account = ctx
.sequencer_client()
.get_account(account_id)
.await?
.account;
let post_state_account = ctx.sequencer_client().get_account(account_id).await?;
assert_eq!(post_state_account.program_owner, data_changer.id());
assert_eq!(post_state_account.balance, 0);
assert_eq!(post_state_account.data.as_ref(), &[0]);
assert_eq!(post_state_account.nonce, 0);
assert_eq!(post_state_account.nonce.0, 1);
info!("Successfully deployed and executed program");
+269 -56
View File
@@ -14,6 +14,7 @@ use integration_tests::{
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::program::Program;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
use wallet::cli::{
@@ -78,8 +79,10 @@ async fn create_and_transfer_public_token() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(definition_account_id),
supply_account_id: format_public_account_id(supply_account_id),
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: name.clone(),
total_supply,
};
@@ -92,8 +95,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?
.account;
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, Program::token().id());
@@ -110,8 +112,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id)
.await?
.account;
.await?;
// The account must be owned by the token program
assert_eq!(supply_acc.program_owner, Program::token().id());
@@ -127,8 +128,10 @@ async fn create_and_transfer_public_token() -> Result<()> {
// Transfer 7 tokens from supply_acc to recipient_account_id
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(supply_account_id),
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@@ -143,8 +146,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id)
.await?
.account;
.await?;
assert_eq!(supply_acc.program_owner, Program::token().id());
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
@@ -159,8 +161,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id)
.await?
.account;
.await?;
assert_eq!(recipient_acc.program_owner, Program::token().id());
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
@@ -174,8 +175,10 @@ async fn create_and_transfer_public_token() -> Result<()> {
// Burn 3 tokens from recipient_acc
let burn_amount = 3;
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: format_public_account_id(definition_account_id),
holder: format_public_account_id(recipient_account_id),
definition: Some(format_public_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_public_account_id(recipient_account_id)),
holder_label: None,
amount: burn_amount,
};
@@ -188,8 +191,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?
.account;
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
@@ -205,8 +207,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id)
.await?
.account;
.await?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
@@ -220,8 +221,10 @@ async fn create_and_transfer_public_token() -> Result<()> {
// Mint 10 tokens at recipient_acc
let mint_amount = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_public_account_id(definition_account_id),
definition: Some(format_public_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_public_account_id(recipient_account_id)),
holder_label: None,
holder_npk: None,
holder_vpk: None,
amount: mint_amount,
@@ -236,8 +239,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?
.account;
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
@@ -253,8 +255,7 @@ async fn create_and_transfer_public_token() -> Result<()> {
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id)
.await?
.account;
.await?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
@@ -326,8 +327,10 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(definition_account_id),
supply_account_id: format_private_account_id(supply_account_id),
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
name: name.clone(),
total_supply,
};
@@ -341,8 +344,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?
.account;
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, Program::token().id());
@@ -364,8 +366,10 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
// Transfer 7 tokens from supply_acc to recipient_account_id
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(supply_account_id),
from: Some(format_private_account_id(supply_account_id)),
from_label: None,
to: Some(format_private_account_id(recipient_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@@ -391,8 +395,10 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
// Burn 3 tokens from recipient_acc
let burn_amount = 3;
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: format_public_account_id(definition_account_id),
holder: format_private_account_id(recipient_account_id),
definition: Some(format_public_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_private_account_id(recipient_account_id)),
holder_label: None,
amount: burn_amount,
};
@@ -405,8 +411,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?
.account;
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
@@ -484,8 +489,10 @@ async fn create_token_with_private_definition() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(definition_account_id),
supply_account_id: format_public_account_id(supply_account_id),
definition_account_id: Some(format_private_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: name.clone(),
total_supply,
};
@@ -506,8 +513,7 @@ async fn create_token_with_private_definition() -> Result<()> {
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id)
.await?
.account;
.await?;
assert_eq!(supply_acc.program_owner, Program::token().id());
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
@@ -554,8 +560,10 @@ async fn create_token_with_private_definition() -> Result<()> {
// Mint to public account
let mint_amount_public = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(definition_account_id),
definition: Some(format_private_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_public_account_id(recipient_account_id_public)),
holder_label: None,
holder_npk: None,
holder_vpk: None,
amount: mint_amount_public,
@@ -586,8 +594,7 @@ async fn create_token_with_private_definition() -> Result<()> {
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_public)
.await?
.account;
.await?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
@@ -601,8 +608,10 @@ async fn create_token_with_private_definition() -> Result<()> {
// Mint to private account
let mint_amount_private = 5;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(definition_account_id),
definition: Some(format_private_account_id(definition_account_id)),
definition_label: None,
holder: Some(format_private_account_id(recipient_account_id_private)),
holder_label: None,
holder_npk: None,
holder_vpk: None,
amount: mint_amount_private,
@@ -680,8 +689,10 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(definition_account_id),
supply_account_id: format_private_account_id(supply_account_id),
definition_account_id: Some(format_private_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
name,
total_supply,
};
@@ -739,8 +750,10 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
// Transfer tokens
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(supply_account_id),
from: Some(format_private_account_id(supply_account_id)),
from_label: None,
to: Some(format_private_account_id(recipient_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@@ -852,8 +865,10 @@ async fn shielded_token_transfer() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(definition_account_id),
supply_account_id: format_public_account_id(supply_account_id),
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name,
total_supply,
};
@@ -866,8 +881,10 @@ async fn shielded_token_transfer() -> Result<()> {
// Perform shielded transfer: public supply -> private recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(supply_account_id),
from: Some(format_public_account_id(supply_account_id)),
from_label: None,
to: Some(format_private_account_id(recipient_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@@ -882,8 +899,7 @@ async fn shielded_token_transfer() -> Result<()> {
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id)
.await?
.account;
.await?;
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
@@ -975,8 +991,10 @@ async fn deshielded_token_transfer() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(definition_account_id),
supply_account_id: format_private_account_id(supply_account_id),
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
name,
total_supply,
};
@@ -989,8 +1007,10 @@ async fn deshielded_token_transfer() -> Result<()> {
// Perform deshielded transfer: private supply -> public recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(supply_account_id),
from: Some(format_private_account_id(supply_account_id)),
from_label: None,
to: Some(format_public_account_id(recipient_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: transfer_amount,
@@ -1026,8 +1046,7 @@ async fn deshielded_token_transfer() -> Result<()> {
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id)
.await?
.account;
.await?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
token_holding,
@@ -1082,8 +1101,10 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(definition_account_id),
supply_account_id: format_private_account_id(supply_account_id),
definition_account_id: Some(format_private_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_private_account_id(supply_account_id)),
supply_account_label: None,
name,
total_supply,
};
@@ -1121,9 +1142,11 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
// Mint using claiming path (foreign account)
let mint_amount = 9;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(definition_account_id),
definition: Some(format_private_account_id(definition_account_id)),
definition_label: None,
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)),
holder_label: None,
holder_npk: Some(hex::encode(holder_keys.nullifier_public_key.0)),
holder_vpk: Some(hex::encode(holder_keys.viewing_public_key.0)),
amount: mint_amount,
};
@@ -1162,3 +1185,193 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
Ok(())
}
#[test]
async fn create_token_using_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create definition and supply accounts with labels
let def_label = "token-definition-label".to_owned();
let supply_label = "token-supply-label".to_owned();
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(def_label.clone()),
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(supply_label.clone()),
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token using account labels instead of IDs
let name = "LABELED TOKEN".to_owned();
let total_supply = 100;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: None,
definition_account_label: Some(def_label),
supply_account_id: None,
supply_account_label: Some(supply_label),
name: name.clone(),
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id)
.await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, Program::token().id());
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name,
total_supply,
metadata_id: None
}
);
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id)
.await?;
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply
}
);
info!("Successfully created token using definition and supply account labels");
Ok(())
}
#[test]
async fn transfer_token_using_from_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create definition account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create supply account with a label
let supply_label = "token-supply-sender".to_owned();
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: Some(supply_label.clone()),
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create recipient account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: None,
label: None,
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token
let total_supply = 50;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: Some(format_public_account_id(definition_account_id)),
definition_account_label: None,
supply_account_id: Some(format_public_account_id(supply_account_id)),
supply_account_label: None,
name: "LABEL TEST TOKEN".to_owned(),
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Transfer token using from_label instead of from
let transfer_amount = 20;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: None,
from_label: Some(supply_label),
to: Some(format_public_account_id(recipient_account_id)),
to_label: None,
to_npk: None,
to_vpk: None,
amount: transfer_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id)
.await?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount
}
);
info!("Successfully transferred token using from_label");
Ok(())
}
+11 -16
View File
@@ -13,6 +13,7 @@ use std::time::{Duration, Instant};
use anyhow::Result;
use bytesize::ByteSize;
use common::transaction::NSSATransaction;
use integration_tests::{
TestContext,
config::{InitialData, SequencerPartialConfig},
@@ -27,9 +28,10 @@ use nssa::{
};
use nssa_core::{
MembershipProof, NullifierPublicKey,
account::{AccountWithMetadata, data::Data},
account::{AccountWithMetadata, Nonce, data::Data},
encryption::ViewingPublicKey,
};
use sequencer_service_rpc::RpcClient as _;
use tokio::test;
pub(crate) struct TpsTestManager {
@@ -78,7 +80,7 @@ impl TpsTestManager {
let message = putx::Message::try_new(
program.id(),
[pair[0].1, pair[1].1].to_vec(),
[0_u128].to_vec(),
[Nonce(0_u128)].to_vec(),
amount,
)
.unwrap();
@@ -107,7 +109,7 @@ impl TpsTestManager {
let key_chain = KeyChain::new_os_random();
let account = Account {
balance: 100,
nonce: 0xdead_beef,
nonce: Nonce(0xdead_beef),
program_owner: Program::authenticated_transfer_program().id(),
data: Data::default(),
};
@@ -153,10 +155,9 @@ pub async fn tps_test() -> Result<()> {
for (i, tx) in txs.into_iter().enumerate() {
let tx_hash = ctx
.sequencer_client()
.send_tx_public(tx)
.send_transaction(NSSATransaction::Public(tx))
.await
.unwrap()
.tx_hash;
.unwrap();
info!("Sent tx {i}");
tx_hashes.push(tx_hash);
}
@@ -170,15 +171,13 @@ pub async fn tps_test() -> Result<()> {
let tx_obj = ctx
.sequencer_client()
.get_transaction_by_hash(*tx_hash)
.get_transaction(*tx_hash)
.await
.inspect_err(|err| {
log::warn!("Failed to get transaction by hash {tx_hash} with error: {err:#?}");
});
if let Ok(tx_obj) = tx_obj
&& tx_obj.transaction.is_some()
{
if tx_obj.is_ok_and(|opt| opt.is_some()) {
info!("Found tx {i} with hash {tx_hash}");
break;
}
@@ -216,7 +215,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
let sender_pre = AccountWithMetadata::new(
Account {
balance: 100,
nonce: 0xdead_beef,
nonce: Nonce(0xdead_beef),
program_owner: program.id(),
data: Data::default(),
},
@@ -250,11 +249,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![1, 2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(sender_npk.clone(), sender_ss),
(recipient_npk.clone(), recipient_ss),
],
vec![(sender_npk, sender_ss), (recipient_npk, recipient_ss)],
vec![sender_nsk],
vec![Some(proof)],
&program.into(),
+75 -94
View File
@@ -24,7 +24,6 @@ use log::info;
use nssa::{Account, AccountId, PrivateKey, PublicKey, program::Program};
use nssa_core::program::DEFAULT_PROGRAM_ID;
use tempfile::tempdir;
use wallet::WalletCore;
use wallet_ffi::{
FfiAccount, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey,
FfiTransferResult, WalletHandle, error,
@@ -211,14 +210,6 @@ fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandl
})
}
fn new_wallet_rust_with_default_config(password: &str) -> Result<WalletCore> {
let tempdir = tempdir()?;
let config_path = tempdir.path().join("wallet_config.json");
let storage_path = tempdir.path().join("storage.json");
WalletCore::new_init_storage(config_path, storage_path, None, password.to_owned())
}
fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> {
let config_path = home.join("wallet_config.json");
let storage_path = home.join("storage.json");
@@ -232,19 +223,8 @@ fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> {
fn wallet_ffi_create_public_accounts() -> Result<()> {
let password = "password_for_tests";
let n_accounts = 10;
// First `n_accounts` public accounts created with Rust wallet
let new_public_account_ids_rust = {
let mut account_ids = Vec::new();
let mut wallet_rust = new_wallet_rust_with_default_config(password)?;
for _ in 0..n_accounts {
let account_id = wallet_rust.create_new_account_public(None).0;
account_ids.push(*account_id.value());
}
account_ids
};
// First `n_accounts` public accounts created with wallet FFI
// Create `n_accounts` public accounts with wallet FFI
let new_public_account_ids_ffi = unsafe {
let mut account_ids = Vec::new();
@@ -258,7 +238,20 @@ fn wallet_ffi_create_public_accounts() -> Result<()> {
account_ids
};
assert_eq!(new_public_account_ids_ffi, new_public_account_ids_rust);
// All returned IDs must be unique and non-zero
assert_eq!(new_public_account_ids_ffi.len(), n_accounts);
let unique: HashSet<_> = new_public_account_ids_ffi.iter().collect();
assert_eq!(
unique.len(),
n_accounts,
"Duplicate public account IDs returned"
);
assert!(
new_public_account_ids_ffi
.iter()
.all(|id| *id != [0_u8; 32]),
"Zero account ID returned"
);
Ok(())
}
@@ -267,19 +260,7 @@ fn wallet_ffi_create_public_accounts() -> Result<()> {
fn wallet_ffi_create_private_accounts() -> Result<()> {
let password = "password_for_tests";
let n_accounts = 10;
// First `n_accounts` private accounts created with Rust wallet
let new_private_account_ids_rust = {
let mut account_ids = Vec::new();
let mut wallet_rust = new_wallet_rust_with_default_config(password)?;
for _ in 0..n_accounts {
let account_id = wallet_rust.create_new_account_private(None).0;
account_ids.push(*account_id.value());
}
account_ids
};
// First `n_accounts` private accounts created with wallet FFI
// Create `n_accounts` private accounts with wallet FFI
let new_private_account_ids_ffi = unsafe {
let mut account_ids = Vec::new();
@@ -293,7 +274,20 @@ fn wallet_ffi_create_private_accounts() -> Result<()> {
account_ids
};
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust);
// All returned IDs must be unique and non-zero
assert_eq!(new_private_account_ids_ffi.len(), n_accounts);
let unique: HashSet<_> = new_private_account_ids_ffi.iter().collect();
assert_eq!(
unique.len(),
n_accounts,
"Duplicate private account IDs returned"
);
assert!(
new_private_account_ids_ffi
.iter()
.all(|id| *id != [0_u8; 32]),
"Zero account ID returned"
);
Ok(())
}
@@ -349,28 +343,23 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
fn test_wallet_ffi_list_accounts() -> Result<()> {
let password = "password_for_tests";
// Create the wallet FFI
let wallet_ffi_handle = unsafe {
// Create the wallet FFI and track which account IDs were created as public/private
let (wallet_ffi_handle, created_public_ids, created_private_ids) = unsafe {
let handle = new_wallet_ffi_with_default_config(password)?;
// Create 5 public accounts and 5 private accounts
let mut public_ids: Vec<[u8; 32]> = Vec::new();
let mut private_ids: Vec<[u8; 32]> = Vec::new();
// Create 5 public accounts and 5 private accounts, recording their IDs
for _ in 0..5 {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_public(handle, &raw mut out_account_id);
public_ids.push(out_account_id.data);
wallet_ffi_create_account_private(handle, &raw mut out_account_id);
private_ids.push(out_account_id.data);
}
handle
};
// Create the wallet Rust
let wallet_rust = {
let mut wallet = new_wallet_rust_with_default_config(password)?;
// Create 5 public accounts and 5 private accounts
for _ in 0..5 {
wallet.create_new_account_public(None);
wallet.create_new_account_private(None);
}
wallet
(handle, public_ids, private_ids)
};
// Get the account list with FFI method
@@ -380,15 +369,6 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
out_list
};
let wallet_rust_account_ids = wallet_rust
.storage()
.user_data
.account_ids()
.collect::<Vec<_>>();
// Assert same number of elements between Rust and FFI result
assert_eq!(wallet_rust_account_ids.len(), wallet_ffi_account_list.count);
let wallet_ffi_account_list_slice = unsafe {
core::slice::from_raw_parts(
wallet_ffi_account_list.entries,
@@ -396,37 +376,38 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
)
};
// Assert same account ids between Rust and FFI result
assert_eq!(
wallet_rust_account_ids
.iter()
.map(nssa::AccountId::value)
.collect::<HashSet<_>>(),
wallet_ffi_account_list_slice
.iter()
.map(|entry| &entry.account_id.data)
.collect::<HashSet<_>>()
);
// All created accounts must appear in the list
let listed_public_ids: HashSet<[u8; 32]> = wallet_ffi_account_list_slice
.iter()
.filter(|e| e.is_public)
.map(|e| e.account_id.data)
.collect();
let listed_private_ids: HashSet<[u8; 32]> = wallet_ffi_account_list_slice
.iter()
.filter(|e| !e.is_public)
.map(|e| e.account_id.data)
.collect();
// Assert `is_pub` flag is correct in the FFI result
for entry in wallet_ffi_account_list_slice {
let account_id = AccountId::new(entry.account_id.data);
let is_pub_default_in_rust_wallet = wallet_rust
.storage()
.user_data
.default_pub_account_signing_keys
.contains_key(&account_id);
let is_pub_key_tree_wallet_rust = wallet_rust
.storage()
.user_data
.public_key_tree
.account_id_map
.contains_key(&account_id);
let is_public_in_rust_wallet = is_pub_default_in_rust_wallet || is_pub_key_tree_wallet_rust;
assert_eq!(entry.is_public, is_public_in_rust_wallet);
for id in &created_public_ids {
assert!(
listed_public_ids.contains(id),
"Created public account not found in list with is_public=true"
);
}
for id in &created_private_ids {
assert!(
listed_private_ids.contains(id),
"Created private account not found in list with is_public=false"
);
}
// Total listed accounts must be at least the number we created
assert!(
wallet_ffi_account_list.count >= created_public_ids.len() + created_private_ids.len(),
"Listed account count ({}) is less than the number of created accounts ({})",
wallet_ffi_account_list.count,
created_public_ids.len() + created_private_ids.len()
);
unsafe {
wallet_ffi_free_account_list(&raw mut wallet_ffi_account_list);
@@ -491,7 +472,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
);
assert_eq!(account.balance, 10000);
assert!(account.data.is_empty());
assert_eq!(account.nonce, 0);
assert_eq!(account.nonce.0, 0);
unsafe {
wallet_ffi_free_account_data(&raw mut out_account);
@@ -528,7 +509,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> {
);
assert_eq!(account.balance, 10000);
assert!(account.data.is_empty());
assert_eq!(account.nonce, 0);
assert_eq!(account.nonce, 0_u128.into());
unsafe {
wallet_ffi_free_account_data(&raw mut out_account);
@@ -606,7 +587,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
.unwrap()
.0;
let expected_npk = &key_chain.nullifer_public_key;
let expected_npk = &key_chain.nullifier_public_key;
let expected_vpk = &key_chain.viewing_public_key;
assert_eq!(&keys.npk(), expected_npk);
@@ -924,7 +905,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
let to = FfiBytes32::from_bytes([37; 32]);
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
let amount: [u8; 16] = 100_u128.to_le_bytes();
let mut transfer_result = FfiTransferResult::default();
@@ -967,7 +948,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
};
assert_eq!(from_balance, 9900);
assert_eq!(to_balance, 100);
assert_eq!(to_balance, 10100);
unsafe {
wallet_ffi_free_transfer_result(&raw mut transfer_result);