Merge pull request #671 from logos-blockchain/Pravdyvy/builtin-programs-integration-tests-removal

ci(integration_tests): Tests removal + Simple retry on fixtures
This commit is contained in:
Pravdyvy 2026-08-04 17:52:34 +03:00 committed by GitHub
commit acd269649a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 8 additions and 2188 deletions

View File

@ -214,7 +214,14 @@ jobs:
with: with:
image: ${{ needs.ci-image.outputs.image }} image: ${{ needs.ci-image.outputs.image }}
env: RISC0_DEV_MODE=1 env: RISC0_DEV_MODE=1
run: cargo nextest archive -p integration_tests --archive-file integration-tests.tar.zst --no-pager run: |
for i in 1 2 3; do
cargo nextest archive -p integration_tests --archive-file integration-tests.tar.zst --no-pager && break
echo "::warning:: Attempt $i failed, cleaning up and retrying..." >&2
rm -rf target/debug/deps/*.o target/debug/incremental 2>/dev/null || true
cargo clean -p integration_tests 2>/dev/null || true
sleep 5
done
- name: Upload integration test archive - name: Upload integration test archive
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

2
Cargo.lock generated
View File

@ -4372,7 +4372,6 @@ name = "integration_tests"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"associated_token_account_core",
"authenticated_transfer_core", "authenticated_transfer_core",
"bridge_core", "bridge_core",
"bridge_lock_core", "bridge_lock_core",
@ -4402,7 +4401,6 @@ dependencies = [
"test_fixtures", "test_fixtures",
"test_programs", "test_programs",
"testnet_initial_state", "testnet_initial_state",
"token_core",
"tokio", "tokio",
"vault_core", "vault_core",
"wallet", "wallet",

View File

@ -17,8 +17,6 @@ wallet.workspace = true
common.workspace = true common.workspace = true
key_protocol.workspace = true key_protocol.workspace = true
serde_json.workspace = true serde_json.workspace = true
token_core.workspace = true
associated_token_account_core.workspace = true
vault_core.workspace = true vault_core.workspace = true
faucet_core.workspace = true faucet_core.workspace = true
bridge_core.workspace = true bridge_core.workspace = true

View File

@ -1,386 +0,0 @@
#![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 integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, create_token, get_account, new_account,
public_mention, token_send_claiming_new_account,
};
use log::info;
use tokio::test;
use wallet::{
account::Label,
cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::amm::AmmProgramAgnosticSubcommand,
},
};
#[test]
async fn amm_public() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create new account for the token definition
let definition_account_id_1 = new_account(&mut ctx, false, None).await?;
// Create new account for the token supply holder
let supply_account_id_1 = new_account(&mut ctx, false, None).await?;
// Create new account for receiving a token transaction
let recipient_account_id_1 = new_account(&mut ctx, false, None).await?;
// Create new account for the token definition
let definition_account_id_2 = new_account(&mut ctx, false, None).await?;
// Create new account for the token supply holder
let supply_account_id_2 = new_account(&mut ctx, false, None).await?;
// Create new account for receiving a token transaction
let recipient_account_id_2 = new_account(&mut ctx, false, None).await?;
// Create new token
create_token(
&mut ctx,
public_mention(definition_account_id_1),
public_mention(supply_account_id_1),
"A NAM1".to_owned(),
37,
)
.await?;
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1`.
// `recipient_account_id_1` is still unclaimed, so this bypasses the wallet CLI (which never
// signs with the recipient's key) and signs with the recipient's own key directly.
token_send_claiming_new_account(&mut ctx, supply_account_id_1, recipient_account_id_1, 7)
.await?;
// Create new token
create_token(
&mut ctx,
public_mention(definition_account_id_2),
public_mention(supply_account_id_2),
"A NAM2".to_owned(),
37,
)
.await?;
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2`.
// `recipient_account_id_2` is still unclaimed, so this bypasses the wallet CLI the same way.
token_send_claiming_new_account(&mut ctx, supply_account_id_2, recipient_account_id_2, 7)
.await?;
info!("=================== SETUP FINISHED ===============");
// Create new AMM
// Setup accounts
// Create new account for the user holding lp
let user_holding_lp = new_account(&mut ctx, false, None).await?;
// Send creation tx
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
user_holding_lp: public_mention(user_holding_lp),
balance_a: 3,
balance_b: 3,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = get_account(&ctx, recipient_account_id_1).await?;
let user_holding_b_acc = get_account(&ctx, recipient_account_id_2).await?;
let user_holding_lp_acc = get_account(&ctx, user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("=================== AMM DEFINITION FINISHED ===============");
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::SwapExactInput {
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_1,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = get_account(&ctx, recipient_account_id_1).await?;
let user_holding_b_acc = get_account(&ctx, recipient_account_id_2).await?;
let user_holding_lp_acc = get_account(&ctx, user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
2
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
5
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("=================== FIRST SWAP FINISHED ===============");
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::SwapExactInput {
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_2,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = get_account(&ctx, recipient_account_id_1).await?;
let user_holding_b_acc = get_account(&ctx, recipient_account_id_2).await?;
let user_holding_lp_acc = get_account(&ctx, user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
3
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("=================== SECOND SWAP FINISHED ===============");
// Add liquidity
let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity {
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
user_holding_lp: public_mention(user_holding_lp),
min_amount_lp: 1,
max_amount_a: 2,
max_amount_b: 2,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = get_account(&ctx, recipient_account_id_1).await?;
let user_holding_b_acc = get_account(&ctx, recipient_account_id_2).await?;
let user_holding_lp_acc = get_account(&ctx, user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
3
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
1
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
4
);
info!("=================== ADD LIQ FINISHED ===============");
// Remove liquidity
let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity {
user_holding_a: public_mention(recipient_account_id_1),
user_holding_b: public_mention(recipient_account_id_2),
user_holding_lp: public_mention(user_holding_lp),
balance_lp: 2,
min_amount_a: 1,
min_amount_b: 1,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = get_account(&ctx, recipient_account_id_1).await?;
let user_holding_b_acc = get_account(&ctx, recipient_account_id_2).await?;
let user_holding_lp_acc = get_account(&ctx, user_holding_lp).await?;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
5
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
2
);
info!("Success!");
Ok(())
}
#[test]
async fn amm_new_pool_using_labels() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token 1 accounts
let definition_account_id_1 = new_account(&mut ctx, false, None).await?;
let supply_account_id_1 = new_account(&mut ctx, false, None).await?;
// Create holding_a with a label
let holding_a_label = Label::new("amm-holding-a-label");
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(Label::new(holding_a_label.clone())),
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token 2 accounts
let definition_account_id_2 = new_account(&mut ctx, false, None).await?;
let supply_account_id_2 = new_account(&mut ctx, false, None).await?;
// Create holding_b with a label
let holding_b_label = Label::new("amm-holding-b-label");
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(Label::new(holding_b_label.clone())),
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create holding_lp with a label
let holding_lp_label = Label::new("amm-holding-lp-label");
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(Label::new(holding_lp_label.clone())),
})),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token 1 and distribute to holding_a
create_token(
&mut ctx,
public_mention(definition_account_id_1),
public_mention(supply_account_id_1),
"TOKEN1".to_owned(),
10,
)
.await?;
// `holding_a_id` is still unclaimed, so bypass the wallet CLI (see
// `token_send_claiming_new_account`'s docs for why).
token_send_claiming_new_account(&mut ctx, supply_account_id_1, holding_a_id, 5).await?;
// Create token 2 and distribute to holding_b
create_token(
&mut ctx,
public_mention(definition_account_id_2),
public_mention(supply_account_id_2),
"TOKEN2".to_owned(),
10,
)
.await?;
// `holding_b_id` is still unclaimed, so bypass the wallet CLI the same way.
token_send_claiming_new_account(&mut ctx, supply_account_id_2, holding_b_id, 5).await?;
// Create AMM pool using account labels instead of IDs
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: holding_a_label.into(),
user_holding_b: holding_b_label.into(),
user_holding_lp: holding_lp_label.into(),
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 = get_account(&ctx, 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(())
}

View File

@ -1,562 +0,0 @@
#![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 associated_token_account_core::{compute_ata_seed, get_associated_token_account_id};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, create_token, get_account, new_account,
private_mention, public_mention, token_send, verify_commitment_is_in_state,
};
use log::info;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
use wallet::cli::{Command, programs::ata::AtaSubcommand};
#[test]
async fn create_ata_initializes_holding_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let definition_account_id = new_account(&mut ctx, false, None).await?;
let supply_account_id = new_account(&mut ctx, false, None).await?;
let owner_account_id = new_account(&mut ctx, false, None).await?;
// Create a fungible token
let total_supply = 100_u128;
create_token(
&mut ctx,
public_mention(definition_account_id),
public_mention(supply_account_id),
"TEST".to_owned(),
total_supply,
)
.await?;
// Create the ATA for owner + definition
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: public_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.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 = programs::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, programs::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_account(&mut ctx, false, None).await?;
let supply_account_id = new_account(&mut ctx, false, None).await?;
let owner_account_id = new_account(&mut ctx, false, None).await?;
// Create a fungible token
create_token(
&mut ctx,
public_mention(definition_account_id),
public_mention(supply_account_id),
"TEST".to_owned(),
100,
)
.await?;
// Create the ATA once
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: public_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.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: public_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.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 = programs::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, programs::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_account(&mut ctx, false, None).await?;
let supply_account_id = new_account(&mut ctx, false, None).await?;
let sender_account_id = new_account(&mut ctx, false, None).await?;
let recipient_account_id = new_account(&mut ctx, false, None).await?;
let total_supply = 1000_u128;
// Create a fungible token, supply goes to supply_account_id
create_token(
&mut ctx,
public_mention(definition_account_id),
public_mention(supply_account_id),
"TEST".to_owned(),
total_supply,
)
.await?;
// Derive ATA addresses
let ata_program_id = programs::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: public_mention(sender_account_id),
token_definition: definition_account_id,
}),
)
.await?;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: public_mention(recipient_account_id),
token_definition: definition_account_id,
}),
)
.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;
token_send(
&mut ctx,
public_mention(supply_account_id),
public_mention(sender_ata_id),
fund_amount,
)
.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: public_mention(sender_account_id),
token_definition: definition_account_id,
to: recipient_ata_id,
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 = get_account(&ctx, 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 = get_account(&ctx, 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: public_mention(sender_account_id),
token_definition: definition_account_id,
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 = get_account(&ctx, 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 = get_account(&ctx, 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_account(&mut ctx, false, None).await?;
let supply_account_id = new_account(&mut ctx, false, None).await?;
let owner_account_id = new_account(&mut ctx, true, None).await?;
// Create a fungible token
create_token(
&mut ctx,
public_mention(definition_account_id),
public_mention(supply_account_id),
"TEST".to_owned(),
100,
)
.await?;
// Create the ATA for the private owner + definition
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: private_mention(owner_account_id),
token_definition: definition_account_id,
}),
)
.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 = programs::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, programs::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_account(&mut ctx, false, None).await?;
let supply_account_id = new_account(&mut ctx, false, None).await?;
let sender_account_id = new_account(&mut ctx, true, None).await?;
let recipient_account_id = new_account(&mut ctx, false, None).await?;
let total_supply = 1000_u128;
// Create a fungible token
create_token(
&mut ctx,
public_mention(definition_account_id),
public_mention(supply_account_id),
"TEST".to_owned(),
total_supply,
)
.await?;
// Derive ATA addresses
let ata_program_id = programs::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: private_mention(sender_account_id),
token_definition: definition_account_id,
}),
)
.await?;
wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Ata(AtaSubcommand::Create {
owner: public_mention(recipient_account_id),
token_definition: definition_account_id,
}),
)
.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;
token_send(
&mut ctx,
public_mention(supply_account_id),
public_mention(sender_ata_id),
fund_amount,
)
.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: private_mention(sender_account_id),
token_definition: definition_account_id,
to: recipient_ata_id,
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 = get_account(&ctx, 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 = get_account(&ctx, 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_account(&mut ctx, false, None).await?;
let supply_account_id = new_account(&mut ctx, false, None).await?;
let holder_account_id = new_account(&mut ctx, true, None).await?;
let total_supply = 500_u128;
// Create a fungible token
create_token(
&mut ctx,
public_mention(definition_account_id),
public_mention(supply_account_id),
"TEST".to_owned(),
total_supply,
)
.await?;
// Derive holder's ATA address
let ata_program_id = programs::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: private_mention(holder_account_id),
token_definition: definition_account_id,
}),
)
.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;
token_send(
&mut ctx,
public_mention(supply_account_id),
public_mention(holder_ata_id),
fund_amount,
)
.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: private_mention(holder_account_id),
token_definition: definition_account_id,
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 = get_account(&ctx, 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 = get_account(&ctx, 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(())
}

View File

@ -1,239 +0,0 @@
#![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 integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, account_balance, new_account, private_mention,
public_mention, sync_private, verify_commitment_is_in_state, wait_for_indexer_to_catch_up,
};
use log::info;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
programs::{
native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand,
},
};
#[test]
async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()> {
let mut ctx = TestContext::new().await?;
let winner_account_id = new_account(&mut ctx, false, None).await?;
let pinata_balance_pre = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: public_mention(winner_account_id),
}),
)
.await;
assert!(
claim_result.is_err(),
"Expected uninitialized account error"
);
let err = claim_result.unwrap_err().to_string();
assert!(
err.contains("wallet auth-transfer init --account-id Public/"),
"Expected init guidance, got: {err}",
);
let pinata_balance_post = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
assert_eq!(pinata_balance_post, pinata_balance_pre);
Ok(())
}
#[test]
async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()> {
let mut ctx = TestContext::new().await?;
let winner_account_id = new_account(&mut ctx, true, None).await?;
let pinata_balance_pre = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
let claim_result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: private_mention(winner_account_id),
}),
)
.await;
assert!(
claim_result.is_err(),
"Expected uninitialized account error"
);
let err = claim_result.unwrap_err().to_string();
assert!(
err.contains("wallet auth-transfer init --account-id Private/"),
"Expected init guidance, got: {err}",
);
let pinata_balance_post = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
assert_eq!(pinata_balance_post, pinata_balance_pre);
Ok(())
}
#[test]
async fn claim_pinata_to_existing_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: public_mention(ctx.existing_public_accounts()[0]),
});
let pinata_balance_pre = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
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 pinata_balance_post = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
let winner_balance_post = account_balance(&ctx, ctx.existing_public_accounts()[0]).await?;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
assert_eq!(winner_balance_post, 10000 + pinata_prize);
info!("Successfully claimed pinata to public account");
Ok(())
}
#[test]
async fn claim_pinata_indexer_keeps_up() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: public_mention(ctx.existing_public_accounts()[0]),
});
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!("Waiting for indexer to parse blocks");
wait_for_indexer_to_catch_up(&ctx).await?;
let winner_ind_state = indexer_service_rpc::RpcClient::get_account(
&**ctx.indexer_client(),
ctx.existing_public_accounts()[0].into(),
)
.await
.unwrap();
let winner_seq_state = sequencer_service_rpc::RpcClient::get_account(
ctx.sequencer_client(),
ctx.existing_public_accounts()[0],
)
.await?;
assert_eq!(winner_ind_state, winner_seq_state.into());
info!("Indexer correctly indexed the pinata claim");
Ok(())
}
#[test]
async fn claim_pinata_to_existing_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: private_mention(ctx.existing_private_accounts()[0]),
});
let pinata_balance_pre = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::TransactionExecuted { tx_hash: _ } = result else {
anyhow::bail!("Expected TransactionExecuted return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Syncing private accounts");
sync_private(&mut ctx).await?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(ctx.existing_private_accounts()[0])
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let pinata_balance_post = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
info!("Successfully claimed pinata to existing private account");
Ok(())
}
#[test]
async fn claim_pinata_to_new_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let pinata_prize = 150;
// Create new private account
let winner_account_id = new_account(&mut ctx, true, None).await?;
// Initialize account under auth transfer program
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: private_mention(winner_account_id),
});
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_commitment = ctx
.wallet()
.get_private_account_commitment(winner_account_id)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Claim pinata to the new private account
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: private_mention(winner_account_id),
});
let pinata_balance_pre = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
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_commitment = ctx
.wallet()
.get_private_account_commitment(winner_account_id)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let pinata_balance_post = account_balance(&ctx, system_accounts::pinata_account_id()).await?;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
info!("Successfully claimed pinata to new private account");
Ok(())
}

View File

@ -1,996 +0,0 @@
#![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 integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, get_account, new_account, private_mention,
public_mention, sync_private, token_send_claiming_new_account, verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use token_core::{TokenDefinition, TokenHolding};
use tokio::test;
use wallet::{
account::Label,
cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::token::TokenProgramAgnosticSubcommand,
},
};
#[test]
async fn create_and_transfer_public_token() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create new account for the token definition
let definition_account_id = new_account(&mut ctx, false, None).await?;
// Create new account for the token supply holder
let supply_account_id = new_account(&mut ctx, false, None).await?;
// Create new account for receiving a token transaction
let recipient_account_id = new_account(&mut ctx, false, None).await?;
// Create new token
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
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;
// Check the status of the token definition account
let definition_acc = get_account(&ctx, definition_account_id).await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, programs::token().id());
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply,
metadata_id: None
}
);
// Check the status of the token holding account with the total supply
let supply_acc = get_account(&ctx, supply_account_id).await?;
// The account must be owned by the token program
assert_eq!(supply_acc.program_owner, programs::token().id());
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply
}
);
// Transfer 7 tokens from supply_acc to recipient_account_id. `recipient_account_id` is
// still unclaimed, so this bypasses the wallet CLI (which never signs with the recipient's
// key) and signs with the recipient's own key directly.
let transfer_amount = 7;
token_send_claiming_new_account(
&mut ctx,
supply_account_id,
recipient_account_id,
transfer_amount,
)
.await?;
// Check the status of the supply account after transfer
let supply_acc = get_account(&ctx, supply_account_id).await?;
assert_eq!(supply_acc.program_owner, programs::token().id());
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 = get_account(&ctx, recipient_account_id).await?;
assert_eq!(recipient_acc.program_owner, programs::token().id());
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: public_mention(definition_account_id),
holder: public_mention(recipient_account_id),
amount: burn_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;
// Check the status of the token definition account after burn
let definition_acc = get_account(&ctx, definition_account_id).await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
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
let recipient_acc = get_account(&ctx, 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 - burn_amount
}
);
// Mint 10 tokens at recipient_acc
let mint_amount = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: public_mention(definition_account_id),
holder: Some(public_mention(recipient_account_id)),
holder_npk: None,
holder_vpk: None,
holder_keys: None,
holder_identifier: None,
amount: mint_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;
// Check the status of the token definition account after mint
let definition_acc = get_account(&ctx, definition_account_id).await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
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
let recipient_acc = get_account(&ctx, 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 - burn_amount + mint_amount
}
);
info!("Successfully created and transferred public token");
Ok(())
}
#[test]
async fn create_and_transfer_token_with_private_supply() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create new account for the token definition (public)
let definition_account_id = new_account(&mut ctx, false, None).await?;
// Create new account for the token supply holder (private)
let supply_account_id = new_account(&mut ctx, true, None).await?;
// Create new account for receiving a token transaction (private)
let recipient_account_id = new_account(&mut ctx, true, None).await?;
// Create new token
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: public_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
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;
// Check the status of the token definition account
let definition_acc = get_account(&ctx, definition_account_id).await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, programs::token().id());
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply,
metadata_id: None
}
);
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(supply_account_id)
.context("Failed to get supply account commitment")?;
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: private_mention(supply_account_id),
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
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 new_commitment1 = ctx
.wallet()
.get_private_account_commitment(supply_account_id)
.context("Failed to get supply account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(recipient_account_id)
.context("Failed to get recipient account commitment")?;
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: public_mention(definition_account_id),
holder: private_mention(recipient_account_id),
amount: burn_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;
// Check the token definition account after burn
let definition_acc = get_account(&ctx, definition_account_id).await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name,
total_supply: total_supply - burn_amount,
metadata_id: None
}
);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(recipient_account_id)
.context("Failed to get recipient account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
// Check the recipient account balance after burn
let recipient_acc = ctx
.wallet()
.get_account_private(recipient_account_id)
.context("Failed to get recipient account")?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: transfer_amount - burn_amount
}
);
info!("Successfully created and transferred token with private supply");
Ok(())
}
#[test]
async fn create_token_with_private_definition() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (private)
let definition_account_id = new_account(&mut ctx, true, Some(ChainIndex::root())).await?;
// Create supply account (public)
let supply_account_id = new_account(&mut ctx, false, Some(ChainIndex::root())).await?;
// Create token with private definition
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: private_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
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;
// Verify private definition commitment
let new_commitment = ctx
.wallet()
.get_private_account_commitment(definition_account_id)
.context("Failed to get definition commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify supply account
let supply_acc = get_account(&ctx, supply_account_id).await?;
assert_eq!(supply_acc.program_owner, programs::token().id());
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 recipient_account_id_private = new_account(&mut ctx, true, None).await?;
// Create public recipient account
let recipient_account_id_public = new_account(&mut ctx, false, None).await?;
// Mint to public account
let mint_amount_public = 10;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: private_mention(definition_account_id),
holder: Some(public_mention(recipient_account_id_public)),
holder_npk: None,
holder_vpk: None,
holder_keys: None,
holder_identifier: None,
amount: mint_amount_public,
};
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;
// Verify definition account has updated supply
let definition_acc = ctx
.wallet()
.get_account_private(definition_account_id)
.context("Failed to get definition account")?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name: name.clone(),
total_supply: total_supply + mint_amount_public,
metadata_id: None
}
);
// Verify public recipient received tokens
let recipient_acc = get_account(&ctx, recipient_account_id_public).await?;
let token_holding = TokenHolding::try_from(&recipient_acc.data)?;
assert_eq!(
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: private_mention(definition_account_id),
holder: Some(private_mention(recipient_account_id_private)),
holder_npk: None,
holder_vpk: None,
holder_keys: None,
holder_identifier: None,
amount: mint_amount_private,
};
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;
// Verify private recipient commitment
let new_commitment = ctx
.wallet()
.get_private_account_commitment(recipient_account_id_private)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify private recipient balance
let recipient_acc_private = ctx
.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!(
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");
Ok(())
}
#[test]
async fn create_token_with_private_definition_and_supply() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (private)
let definition_account_id = new_account(&mut ctx, true, None).await?;
// Create supply account (private)
let supply_account_id = new_account(&mut ctx, true, None).await?;
// Create token with both private definition and supply
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: private_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name,
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;
// Verify definition commitment
let definition_commitment = ctx
.wallet()
.get_private_account_commitment(definition_account_id)
.context("Failed to get definition commitment")?;
assert!(verify_commitment_is_in_state(definition_commitment, ctx.sequencer_client()).await);
// Verify supply commitment
let supply_commitment = ctx
.wallet()
.get_private_account_commitment(supply_account_id)
.context("Failed to get supply commitment")?;
assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await);
// Verify supply balance
let supply_acc = ctx
.wallet()
.get_account_private(supply_account_id)
.context("Failed to get supply account")?;
let token_holding = TokenHolding::try_from(&supply_acc.data)?;
assert_eq!(
token_holding,
TokenHolding::Fungible {
definition_id: definition_account_id,
balance: total_supply
}
);
// Create recipient account
let recipient_account_id = new_account(&mut ctx, true, None).await?;
// Transfer tokens
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: private_mention(supply_account_id),
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
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;
// Verify both commitments updated
let supply_commitment = ctx
.wallet()
.get_private_account_commitment(supply_account_id)
.context("Failed to get supply commitment")?;
assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await);
let recipient_commitment = ctx
.wallet()
.get_private_account_commitment(recipient_account_id)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await);
// Verify balances
let supply_acc = ctx
.wallet()
.get_account_private(supply_account_id)
.context("Failed to get supply account")?;
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")?;
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");
Ok(())
}
#[test]
async fn shielded_token_transfer() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (public)
let definition_account_id = new_account(&mut ctx, false, None).await?;
// Create supply account (public)
let supply_account_id = new_account(&mut ctx, false, None).await?;
// Create recipient account (private) for shielded transfer
let recipient_account_id = new_account(&mut ctx, true, None).await?;
// Create token
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
name,
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;
// Perform shielded transfer: public supply -> private recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: public_mention(supply_account_id),
to: Some(private_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
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;
// Verify supply account balance
let supply_acc = get_account(&ctx, 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 - transfer_amount
}
);
// Verify recipient commitment exists
let new_commitment = ctx
.wallet()
.get_private_account_commitment(recipient_account_id)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify recipient balance
let recipient_acc = ctx
.wallet()
.get_account_private(recipient_account_id)
.context("Failed to get recipient account")?;
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");
Ok(())
}
#[test]
async fn deshielded_token_transfer() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (public)
let definition_account_id = new_account(&mut ctx, false, None).await?;
// Create supply account (private)
let supply_account_id = new_account(&mut ctx, true, None).await?;
// Create recipient account (public) for deshielded transfer
let recipient_account_id = new_account(&mut ctx, false, None).await?;
// Create token with private supply
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: public_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name,
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;
// Perform deshielded transfer: private supply -> public recipient
let transfer_amount = 7;
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: private_mention(supply_account_id),
to: Some(public_mention(recipient_account_id)),
to_npk: None,
to_vpk: None,
to_keys: None,
to_identifier: Some(0),
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;
// Verify supply account commitment exists
let new_commitment = ctx
.wallet()
.get_private_account_commitment(supply_account_id)
.context("Failed to get supply commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify supply balance
let supply_acc = ctx
.wallet()
.get_account_private(supply_account_id)
.context("Failed to get supply account")?;
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 = get_account(&ctx, 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 performed deshielded token transfer");
Ok(())
}
#[test]
async fn token_claiming_path_with_private_accounts() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (private)
let definition_account_id = new_account(&mut ctx, true, None).await?;
// Create supply account (private)
let supply_account_id = new_account(&mut ctx, true, None).await?;
// Create token
let name = "A NAME".to_owned();
let total_supply = 37;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: private_mention(definition_account_id),
supply_account_id: private_mention(supply_account_id),
name,
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;
// Create new private account for claiming path
let recipient_account_id = new_account(&mut ctx, true, None).await?;
// Get keys for foreign mint (claiming path)
let holder = ctx
.wallet()
.storage()
.key_chain()
.private_account(recipient_account_id)
.context("Failed to get private account keys")?;
let holder_keys = holder.key_chain;
let holder_identifier = holder.kind.identifier();
// Mint using claiming path (foreign account)
let mint_amount = 9;
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: private_mention(definition_account_id),
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifier_public_key.0)),
holder_vpk: Some(hex::encode(holder_keys.viewing_public_key.to_bytes())),
holder_keys: None,
holder_identifier: Some(holder_identifier),
amount: mint_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;
// Sync to claim the account
sync_private(&mut ctx).await?;
// Verify commitment exists
let recipient_commitment = ctx
.wallet()
.get_private_account_commitment(recipient_account_id)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await);
// Verify balance
let recipient_acc = ctx
.wallet()
.get_account_private(recipient_account_id)
.context("Failed to get recipient account")?;
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");
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 = Label::new("token-definition-label");
let supply_label = Label::new("token-supply-label");
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(Label::new(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: def_label.into(),
supply_account_id: supply_label.into(),
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 = get_account(&ctx, definition_account_id).await?;
let token_definition = TokenDefinition::try_from(&definition_acc.data)?;
assert_eq!(definition_acc.program_owner, programs::token().id());
assert_eq!(
token_definition,
TokenDefinition::Fungible {
name,
total_supply,
metadata_id: None
}
);
let supply_acc = get_account(&ctx, 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 definition_account_id = new_account(&mut ctx, false, None).await?;
// Create supply account with a label
let supply_label = Label::new("token-supply-sender");
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 recipient_account_id = new_account(&mut ctx, false, None).await?;
// Create token
let total_supply = 50;
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: public_mention(definition_account_id),
supply_account_id: public_mention(supply_account_id),
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;
// Confirm the label resolves to the account created for it.
let resolved_sender = ctx
.wallet()
.storage()
.resolve_label(&supply_label)
.context("supply_label should resolve to an account")?;
assert_eq!(
resolved_sender,
wallet::account::AccountIdWithPrivacy::Public(supply_account_id)
);
// Transfer token from the label-resolved account. `recipient_account_id` is still
// unclaimed, so this bypasses the wallet CLI (which never signs with the recipient's key)
// and signs with the recipient's own key directly.
let transfer_amount = 20;
token_send_claiming_new_account(
&mut ctx,
supply_account_id,
recipient_account_id,
transfer_amount,
)
.await?;
let recipient_acc = get_account(&ctx, 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(())
}