Merge branch 'main' into Pravdyvy/indexer-state-management

This commit is contained in:
Pravdyvy
2026-02-06 17:42:32 +02:00
87 changed files with 4456 additions and 4430 deletions
+4
View File
@@ -2,6 +2,7 @@
name = "integration_tests"
version = "0.1.0"
edition = "2024"
license = { workspace = true }
[dependencies]
nssa_core = { workspace = true, features = ["host"] }
@@ -13,6 +14,9 @@ common.workspace = true
key_protocol.workspace = true
indexer_service.workspace = true
indexer_core.workspace = true
wallet-ffi.workspace = true
serde_json.workspace = true
token_core.workspace = true
url.workspace = true
anyhow.workspace = true
+25 -4
View File
@@ -42,6 +42,7 @@ pub struct TestContext {
wallet: WalletCore,
_sequencer_handle: SequencerHandle,
_indexer_handle: IndexerHandle,
wallet_password: String,
_temp_sequencer_dir: TempDir,
_temp_wallet_dir: TempDir,
}
@@ -87,7 +88,7 @@ impl TestContext {
format!("http://{sequencer_addr}")
};
let (wallet, temp_wallet_dir) = Self::setup_wallet(sequencer_addr.clone())
let (wallet, temp_wallet_dir, wallet_password) = Self::setup_wallet(sequencer_addr.clone())
.await
.context("Failed to setup wallet")?;
@@ -103,6 +104,7 @@ impl TestContext {
Ok(Self {
sequencer_client,
wallet,
wallet_password,
_sequencer_handle,
_indexer_handle,
_temp_sequencer_dir: temp_sequencer_dir,
@@ -130,7 +132,7 @@ impl TestContext {
Ok((sequencer_handle, sequencer_addr, temp_sequencer_dir))
}
async fn setup_wallet(sequencer_addr: String) -> Result<(WalletCore, TempDir)> {
async fn setup_wallet(sequencer_addr: String) -> Result<(WalletCore, TempDir, String)> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let wallet_config_source_path =
PathBuf::from(manifest_dir).join("configs/wallet/wallet_config.json");
@@ -148,11 +150,12 @@ impl TestContext {
..Default::default()
};
let wallet_password = "test_pass".to_owned();
let wallet = WalletCore::new_init_storage(
config_path,
storage_path,
Some(config_overrides),
"test_pass".to_owned(),
wallet_password.clone(),
)
.context("Failed to init wallet")?;
wallet
@@ -160,7 +163,7 @@ impl TestContext {
.await
.context("Failed to store wallet persistent data")?;
Ok((wallet, temp_wallet_dir))
Ok((wallet, temp_wallet_dir, wallet_password))
}
/// Get reference to the wallet.
@@ -168,6 +171,10 @@ impl TestContext {
&self.wallet
}
pub fn wallet_password(&self) -> &str {
&self.wallet_password
}
/// Get mutable reference to the wallet.
pub fn wallet_mut(&mut self) -> &mut WalletCore {
&mut self.wallet
@@ -179,6 +186,20 @@ impl TestContext {
}
}
/// A test context to be used in normal #[test] tests
pub struct BlockingTestContext {
pub ctx: TestContext,
pub runtime: tokio::runtime::Runtime,
}
impl BlockingTestContext {
pub fn new() -> Result<Self> {
let runtime = tokio::runtime::Runtime::new().unwrap();
let ctx = runtime.block_on(TestContext::new())?;
Ok(Self { ctx, runtime })
}
}
pub fn format_public_account_id(account_id: &str) -> String {
format!("Public/{account_id}")
}
+219 -87
View File
@@ -8,6 +8,7 @@ use integration_tests::{
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::program::Program;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
@@ -59,11 +60,13 @@ async fn create_and_transfer_public_token() -> Result<()> {
};
// Create new token
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name: name.clone(),
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -76,16 +79,16 @@ async fn create_and_transfer_public_token() -> Result<()> {
.get_account(definition_account_id.to_string())
.await?
.account;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 bytes)]
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply,
metadata_id: None
}
);
// Check the status of the token holding account with the total supply
@@ -97,24 +100,23 @@ async fn create_and_transfer_public_token() -> Result<()> {
// The account must be owned by the token program
assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token holding account has the following layout:
// [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ]
// First byte of the data equal to 1 means it's a token holding account
assert_eq!(supply_acc.data.as_ref()[0], 1);
// Bytes from 1 to 33 represent the id of the token this account is associated with
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
&supply_acc.data.as_ref()[1..33],
definition_account_id.to_bytes()
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply
}
);
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
// 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.to_string()),
to: Some(format_public_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
amount: transfer_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -129,9 +131,14 @@ async fn create_and_transfer_public_token() -> Result<()> {
.await?
.account;
assert_eq!(supply_acc.program_owner, Program::token().id());
assert_eq!(supply_acc.data[0], 1);
assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes());
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply - transfer_amount
}
);
// Check the status of the recipient account after transfer
let recipient_acc = ctx
@@ -140,15 +147,21 @@ async fn create_and_transfer_public_token() -> Result<()> {
.await?
.account;
assert_eq!(recipient_acc.program_owner, Program::token().id());
assert_eq!(recipient_acc.data[0], 1);
assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes());
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount
}
);
// Burn 3 tokens from recipient_acc
let burn_amount = 3;
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: format_public_account_id(&definition_account_id.to_string()),
holder: format_public_account_id(&recipient_account_id.to_string()),
amount: 3,
amount: burn_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -162,13 +175,15 @@ async fn create_and_transfer_public_token() -> Result<()> {
.get_account(definition_account_id.to_string())
.await?
.account;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply: total_supply - burn_amount,
metadata_id: None
}
);
// Check the status of the recipient account after burn
@@ -177,16 +192,24 @@ async fn create_and_transfer_public_token() -> Result<()> {
.get_account(recipient_account_id.to_string())
.await?
.account;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 4);
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount - burn_amount
}
);
// Mint 10 tokens at recipient_acc
let mint_amount = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_public_account_id(&definition_account_id.to_string()),
holder: Some(format_public_account_id(&recipient_account_id.to_string())),
holder_npk: None,
holder_ipk: None,
amount: 10,
amount: mint_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -200,13 +223,15 @@ async fn create_and_transfer_public_token() -> Result<()> {
.get_account(definition_account_id.to_string())
.await?
.account;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
token_definition,
TokenDefinition::Fungible {
name,
total_supply: total_supply - burn_amount + mint_amount,
metadata_id: None
}
);
// Check the status of the recipient account after mint
@@ -215,10 +240,14 @@ async fn create_and_transfer_public_token() -> Result<()> {
.get_account(recipient_account_id.to_string())
.await?
.account;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
14
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount - burn_amount + mint_amount
}
);
info!("Successfully created and transferred public token");
@@ -270,11 +299,13 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
};
// Create new token
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name: name.clone(),
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -288,14 +319,16 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
.get_account(definition_account_id.to_string())
.await?
.account;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, Program::token().id());
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply,
metadata_id: None
}
);
let new_commitment1 = ctx
@@ -305,12 +338,13 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
// 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.to_string()),
to: Some(format_private_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
amount: transfer_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -331,10 +365,11 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
// Burn 3 tokens from recipient_acc
let burn_amount = 3;
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: format_public_account_id(&definition_account_id.to_string()),
holder: format_private_account_id(&recipient_account_id.to_string()),
amount: 3,
amount: burn_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -348,13 +383,15 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
.get_account(definition_account_id.to_string())
.await?
.account;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
token_definition,
TokenDefinition::Fungible {
name,
total_supply: total_supply - burn_amount,
metadata_id: None
}
);
let new_commitment2 = ctx
@@ -368,10 +405,14 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
4 // 7 - 3
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount - burn_amount
}
);
info!("Successfully created and transferred token with private supply");
@@ -414,11 +455,13 @@ async fn create_token_with_private_definition() -> Result<()> {
};
// Create token with private definition
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name: name.clone(),
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -441,8 +484,14 @@ async fn create_token_with_private_definition() -> Result<()> {
.account;
assert_eq!(supply_acc.program_owner, Program::token().id());
assert_eq!(supply_acc.data.as_ref()[0], 1);
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply
}
);
// Create private recipient account
let result = wallet::cli::execute_subcommand(
@@ -471,6 +520,7 @@ 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.to_string()),
holder: Some(format_public_account_id(
@@ -478,7 +528,7 @@ async fn create_token_with_private_definition() -> Result<()> {
)),
holder_npk: None,
holder_ipk: None,
amount: 10,
amount: mint_amount_public,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -491,10 +541,15 @@ async fn create_token_with_private_definition() -> Result<()> {
.wallet()
.get_account_private(&definition_account_id)
.context("Failed to get definition account")?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
u128::from_le_bytes(definition_acc.data[7..23].try_into()?),
47 // 37 + 10
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply: total_supply + mint_amount_public,
metadata_id: None
}
);
// Verify public recipient received tokens
@@ -503,13 +558,18 @@ async fn create_token_with_private_definition() -> Result<()> {
.get_account(recipient_account_id_public.to_string())
.await?
.account;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
10
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: mint_amount_public
}
);
// Mint to private account
let mint_amount_private = 5;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(&definition_account_id.to_string()),
holder: Some(format_private_account_id(
@@ -517,7 +577,7 @@ async fn create_token_with_private_definition() -> Result<()> {
)),
holder_npk: None,
holder_ipk: None,
amount: 5,
amount: mint_amount_private,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -537,10 +597,14 @@ async fn create_token_with_private_definition() -> Result<()> {
.wallet()
.get_account_private(&recipient_account_id_private)
.context("Failed to get private recipient account")?;
let token_holding = TokenHolding::try_from(&recipient_acc_private.data)?;
assert_eq!(
u128::from_le_bytes(recipient_acc_private.data[33..].try_into()?),
5
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: mint_amount_private
}
);
info!("Successfully created token with private definition and minted to both account types");
@@ -579,11 +643,13 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
};
// Create token with both private definition and supply
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name,
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -610,8 +676,15 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
.wallet()
.get_account_private(&supply_account_id)
.context("Failed to get supply account")?;
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply
}
);
// Create recipient account
let result = wallet::cli::execute_subcommand(
@@ -627,12 +700,13 @@ 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.to_string()),
to: Some(format_private_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
amount: transfer_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -658,13 +732,27 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
.wallet()
.get_account_private(&supply_account_id)
.context("Failed to get supply account")?;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply - transfer_amount
}
);
let recipient_acc = ctx
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
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 created and transferred token with both private definition and supply");
@@ -715,11 +803,13 @@ async fn shielded_token_transfer() -> Result<()> {
};
// Create token
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name,
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -728,12 +818,13 @@ async fn shielded_token_transfer() -> Result<()> {
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Perform shielded transfer: public supply -> private recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(&supply_account_id.to_string()),
to: Some(format_private_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
amount: transfer_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -747,7 +838,14 @@ async fn shielded_token_transfer() -> Result<()> {
.get_account(supply_account_id.to_string())
.await?
.account;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply - transfer_amount
}
);
// Verify recipient commitment exists
let new_commitment = ctx
@@ -761,7 +859,14 @@ async fn shielded_token_transfer() -> Result<()> {
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
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 performed shielded token transfer");
@@ -812,11 +917,13 @@ async fn deshielded_token_transfer() -> Result<()> {
};
// Create token with private supply
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name,
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -825,12 +932,13 @@ async fn deshielded_token_transfer() -> Result<()> {
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Perform deshielded transfer: private supply -> public recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(&supply_account_id.to_string()),
to: Some(format_public_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
amount: transfer_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -850,7 +958,14 @@ async fn deshielded_token_transfer() -> Result<()> {
.wallet()
.get_account_private(&supply_account_id)
.context("Failed to get supply account")?;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply - transfer_amount
}
);
// Verify recipient balance
let recipient_acc = ctx
@@ -858,7 +973,14 @@ async fn deshielded_token_transfer() -> Result<()> {
.get_account(recipient_account_id.to_string())
.await?
.account;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
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 performed deshielded token transfer");
@@ -896,11 +1018,13 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
};
// Create token
let name = "A NAME".to_string();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
name,
total_supply,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -931,12 +1055,13 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
.context("Failed to get private account keys")?;
// Mint using claiming path (foreign account)
let mint_amount = 9;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(&definition_account_id.to_string()),
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)),
holder_ipk: Some(hex::encode(holder_keys.incoming_viewing_public_key.0)),
amount: 9,
amount: mint_amount,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
@@ -960,7 +1085,14 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 9);
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: mint_amount
}
);
info!("Successfully minted tokens using claiming path");
+618
View File
@@ -0,0 +1,618 @@
use std::{
collections::HashSet,
ffi::{CStr, CString, c_char},
io::Write,
time::Duration,
};
use anyhow::Result;
use integration_tests::{
ACC_RECEIVER, ACC_SENDER, ACC_SENDER_PRIVATE, BlockingTestContext,
TIME_TO_WAIT_FOR_BLOCK_SECONDS,
};
use log::info;
use nssa::{Account, AccountId, 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,
};
unsafe extern "C" {
fn wallet_ffi_create_new(
config_path: *const c_char,
storage_path: *const c_char,
password: *const c_char,
) -> *mut WalletHandle;
fn wallet_ffi_destroy(handle: *mut WalletHandle);
fn wallet_ffi_create_account_public(
handle: *mut WalletHandle,
out_account_id: *mut FfiBytes32,
) -> error::WalletFfiError;
fn wallet_ffi_create_account_private(
handle: *mut WalletHandle,
out_account_id: *mut FfiBytes32,
) -> error::WalletFfiError;
fn wallet_ffi_list_accounts(
handle: *mut WalletHandle,
out_list: *mut FfiAccountList,
) -> error::WalletFfiError;
fn wallet_ffi_free_account_list(list: *mut FfiAccountList);
fn wallet_ffi_get_balance(
handle: *mut WalletHandle,
account_id: *const FfiBytes32,
is_public: bool,
out_balance: *mut [u8; 16],
) -> error::WalletFfiError;
fn wallet_ffi_get_account_public(
handle: *mut WalletHandle,
account_id: *const FfiBytes32,
out_account: *mut FfiAccount,
) -> error::WalletFfiError;
fn wallet_ffi_free_account_data(account: *mut FfiAccount);
fn wallet_ffi_get_public_account_key(
handle: *mut WalletHandle,
account_id: *const FfiBytes32,
out_public_key: *mut FfiPublicAccountKey,
) -> error::WalletFfiError;
fn wallet_ffi_get_private_account_keys(
handle: *mut WalletHandle,
account_id: *const FfiBytes32,
out_keys: *mut FfiPrivateAccountKeys,
) -> error::WalletFfiError;
fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys);
fn wallet_ffi_account_id_to_base58(account_id: *const FfiBytes32) -> *mut std::ffi::c_char;
fn wallet_ffi_free_string(ptr: *mut c_char);
fn wallet_ffi_account_id_from_base58(
base58_str: *const std::ffi::c_char,
out_account_id: *mut FfiBytes32,
) -> error::WalletFfiError;
fn wallet_ffi_transfer_public(
handle: *mut WalletHandle,
from: *const FfiBytes32,
to: *const FfiBytes32,
amount: *const [u8; 16],
out_result: *mut FfiTransferResult,
) -> error::WalletFfiError;
fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult);
fn wallet_ffi_register_public_account(
handle: *mut WalletHandle,
account_id: *const FfiBytes32,
out_result: *mut FfiTransferResult,
) -> error::WalletFfiError;
}
fn new_wallet_ffi_with_test_context_config(ctx: &BlockingTestContext) -> *mut WalletHandle {
let tempdir = tempfile::tempdir().unwrap();
let config_path = tempdir.path().join("wallet_config.json");
let storage_path = tempdir.path().join("storage.json");
let mut config = ctx.ctx.wallet().config().to_owned();
if let Some(config_overrides) = ctx.ctx.wallet().config_overrides().clone() {
config.apply_overrides(config_overrides);
}
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&config_path)
.unwrap();
let config_with_overrides_serialized = serde_json::to_vec_pretty(&config).unwrap();
file.write_all(&config_with_overrides_serialized).unwrap();
let config_path = CString::new(config_path.to_str().unwrap()).unwrap();
let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap();
let password = CString::new(ctx.ctx.wallet_password()).unwrap();
unsafe {
wallet_ffi_create_new(
config_path.as_ptr(),
storage_path.as_ptr(),
password.as_ptr(),
)
}
}
fn new_wallet_ffi_with_default_config(password: &str) -> *mut WalletHandle {
let tempdir = tempdir().unwrap();
let config_path = tempdir.path().join("wallet_config.json");
let storage_path = tempdir.path().join("storage.json");
let config_path_c = CString::new(config_path.to_str().unwrap()).unwrap();
let storage_path_c = CString::new(storage_path.to_str().unwrap()).unwrap();
let password = CString::new(password).unwrap();
unsafe {
wallet_ffi_create_new(
config_path_c.as_ptr(),
storage_path_c.as_ptr(),
password.as_ptr(),
)
}
}
fn new_wallet_rust_with_default_config(password: &str) -> WalletCore {
let tempdir = tempdir().unwrap();
let config_path = tempdir.path().join("wallet_config.json");
let storage_path = tempdir.path().join("storage.json");
WalletCore::new_init_storage(
config_path.to_path_buf(),
storage_path.to_path_buf(),
None,
password.to_string(),
)
.unwrap()
}
#[test]
fn test_wallet_ffi_create_public_accounts() {
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
let new_public_account_ids_ffi = unsafe {
let mut account_ids = Vec::new();
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password);
for _ in 0..n_accounts {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_public(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
account_ids.push(out_account_id.data);
}
wallet_ffi_destroy(wallet_ffi_handle);
account_ids
};
assert_eq!(new_public_account_ids_ffi, new_public_account_ids_rust);
}
#[test]
fn test_wallet_ffi_create_private_accounts() {
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
let new_private_account_ids_ffi = unsafe {
let mut account_ids = Vec::new();
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password);
for _ in 0..n_accounts {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_private(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
account_ids.push(out_account_id.data);
}
wallet_ffi_destroy(wallet_ffi_handle);
account_ids
};
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust)
}
#[test]
fn test_wallet_ffi_list_accounts() {
let password = "password_for_tests";
// Create the wallet FFI
let wallet_ffi_handle = unsafe {
let handle = new_wallet_ffi_with_default_config(password);
// Create 5 public accounts and 5 private accounts
for _ in 0..5 {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_public(handle, (&mut out_account_id) as *mut FfiBytes32);
wallet_ffi_create_account_private(handle, (&mut out_account_id) as *mut FfiBytes32);
}
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
};
// Get the account list with FFI method
let mut wallet_ffi_account_list = unsafe {
let mut out_list = FfiAccountList::default();
wallet_ffi_list_accounts(wallet_ffi_handle, (&mut out_list) as *mut FfiAccountList);
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,
wallet_ffi_account_list.count,
)
};
// Assert same account ids between Rust and FFI result
assert_eq!(
wallet_rust_account_ids
.iter()
.map(|id| id.value())
.collect::<HashSet<_>>(),
wallet_ffi_account_list_slice
.iter()
.map(|entry| &entry.account_id.data)
.collect::<HashSet<_>>()
);
// Assert `is_pub` flag is correct in the FFI result
for entry in wallet_ffi_account_list_slice.iter() {
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);
}
unsafe {
wallet_ffi_free_account_list((&mut wallet_ffi_account_list) as *mut FfiAccountList);
wallet_ffi_destroy(wallet_ffi_handle);
}
}
#[test]
fn test_wallet_ffi_get_balance_public() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ACC_SENDER.parse().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
let balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
true,
(&mut out_balance) as *mut [u8; 16],
);
u128::from_le_bytes(out_balance)
};
assert_eq!(balance, 10000);
info!("Successfully retrieved account balance");
unsafe {
wallet_ffi_destroy(wallet_ffi_handle);
}
Ok(())
}
#[test]
fn test_wallet_ffi_get_account_public() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ACC_SENDER.parse().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
let mut out_account = FfiAccount::default();
let account: Account = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_account_public(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
(&out_account).try_into().unwrap()
};
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(account.balance, 10000);
assert!(account.data.is_empty());
assert_eq!(account.nonce, 0);
unsafe {
wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount);
wallet_ffi_destroy(wallet_ffi_handle);
}
info!("Successfully retrieved account with correct details");
Ok(())
}
#[test]
fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ACC_SENDER.parse().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
let mut out_key = FfiPublicAccountKey::default();
let key: PublicKey = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_public_account_key(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut out_key) as *mut FfiPublicAccountKey,
);
(&out_key).try_into().unwrap()
};
let expected_key = {
let private_key = ctx
.ctx
.wallet()
.get_account_public_signing_key(&account_id)
.unwrap();
PublicKey::new_from_private_key(private_key)
};
assert_eq!(key, expected_key);
info!("Successfully retrieved account key");
unsafe {
wallet_ffi_destroy(wallet_ffi_handle);
}
Ok(())
}
#[test]
fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ACC_SENDER_PRIVATE.parse().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
let mut keys = FfiPrivateAccountKeys::default();
unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_private_account_keys(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut keys) as *mut FfiPrivateAccountKeys,
);
};
let key_chain = &ctx
.ctx
.wallet()
.storage()
.user_data
.get_private_account(&account_id)
.unwrap()
.0;
let expected_npk = &key_chain.nullifer_public_key;
let expected_ivk = &key_chain.incoming_viewing_public_key;
assert_eq!(&keys.npk(), expected_npk);
assert_eq!(&keys.ivk().unwrap(), expected_ivk);
unsafe {
wallet_ffi_free_private_account_keys((&mut keys) as *mut FfiPrivateAccountKeys);
wallet_ffi_destroy(wallet_ffi_handle);
}
info!("Successfully retrieved account keys");
Ok(())
}
#[test]
fn test_wallet_ffi_account_id_to_base58() {
let account_id_str = ACC_SENDER;
let account_id: AccountId = account_id_str.parse().unwrap();
let ffi_bytes: FfiBytes32 = (&account_id).into();
let ptr = unsafe { wallet_ffi_account_id_to_base58((&ffi_bytes) as *const FfiBytes32) };
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
assert_eq!(account_id_str, ffi_result);
unsafe {
wallet_ffi_free_string(ptr);
}
}
#[test]
fn test_wallet_ffi_base58_to_account_id() {
let account_id_str = ACC_SENDER;
let account_id_c_str = CString::new(account_id_str).unwrap();
let account_id: AccountId = unsafe {
let mut out_account_id_bytes = FfiBytes32::default();
wallet_ffi_account_id_from_base58(
account_id_c_str.as_ptr(),
(&mut out_account_id_bytes) as *mut FfiBytes32,
);
out_account_id_bytes.into()
};
let expected_account_id = account_id_str.parse().unwrap();
assert_eq!(account_id, expected_account_id);
}
#[test]
fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
// Create a new uninitialized public account
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
unsafe {
wallet_ffi_create_account_public(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
}
// Check its program owner is the default program id
let account: Account = unsafe {
let mut out_account = FfiAccount::default();
let _result = wallet_ffi_get_account_public(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
(&out_account).try_into().unwrap()
};
assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID);
// Call the init funciton
let mut transfer_result = FfiTransferResult::default();
unsafe {
wallet_ffi_register_public_account(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut transfer_result) as *mut FfiTransferResult,
);
}
info!("Waiting for next block creation");
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
// Check that the program owner is now the authenticated transfer program
let account: Account = unsafe {
let mut out_account = FfiAccount::default();
let _result = wallet_ffi_get_account_public(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
(&out_account).try_into().unwrap()
};
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_destroy(wallet_ffi_handle);
}
Ok(())
}
#[test]
fn test_wallet_ffi_transfer_public() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
let from: FfiBytes32 = (&ACC_SENDER.parse::<AccountId>().unwrap()).into();
let to: FfiBytes32 = (&ACC_RECEIVER.parse::<AccountId>().unwrap()).into();
let amount: [u8; 16] = 100u128.to_le_bytes();
let mut transfer_result = FfiTransferResult::default();
unsafe {
wallet_ffi_transfer_public(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
(&to) as *const FfiBytes32,
(&amount) as *const [u8; 16],
(&mut transfer_result) as *mut FfiTransferResult,
);
}
info!("Waiting for next block creation");
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
let from_balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
true,
(&mut out_balance) as *mut [u8; 16],
);
u128::from_le_bytes(out_balance)
};
let to_balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&to) as *const FfiBytes32,
true,
(&mut out_balance) as *mut [u8; 16],
);
u128::from_le_bytes(out_balance)
};
assert_eq!(from_balance, 9900);
assert_eq!(to_balance, 20100);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_destroy(wallet_ffi_handle);
}
Ok(())
}