1257 lines
35 KiB
Rust
1257 lines
35 KiB
Rust
#![cfg(feature = "test-bpf")]
|
|
#![cfg(twoxtx)]
|
|
|
|
mod program_test;
|
|
use {
|
|
program_test::{TestContext, TokenContext},
|
|
solana_program_test::tokio,
|
|
solana_sdk::{
|
|
epoch_info::EpochInfo, instruction::InstructionError, pubkey::Pubkey, signature::Signer,
|
|
signer::keypair::Keypair, transaction::TransactionError, transport::TransportError,
|
|
},
|
|
spl_token_2022::{
|
|
error::TokenError,
|
|
extension::{
|
|
confidential_transfer::{
|
|
ConfidentialTransferAccount, ConfidentialTransferMint, EncryptedWithheldAmount,
|
|
},
|
|
ExtensionType,
|
|
},
|
|
solana_zk_token_sdk::{
|
|
encryption::{auth_encryption::*, elgamal::*},
|
|
zk_token_elgamal::{self, pod::Zeroable},
|
|
},
|
|
},
|
|
spl_token_client::{
|
|
client::SendTransaction,
|
|
token::{ExtensionInitializationParams, Token, TokenError as TokenClientError},
|
|
},
|
|
std::convert::TryInto,
|
|
};
|
|
|
|
const TEST_MAXIMUM_FEE: u64 = 100;
|
|
const TEST_FEE_BASIS_POINTS: u16 = 250;
|
|
const TEST_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 2;
|
|
|
|
fn test_epoch_info() -> EpochInfo {
|
|
EpochInfo {
|
|
epoch: 0,
|
|
slot_index: 0,
|
|
slots_in_epoch: 0,
|
|
absolute_slot: 0,
|
|
block_height: 0,
|
|
transaction_count: None,
|
|
}
|
|
}
|
|
|
|
struct ConfidentialTransferMintWithKeypairs {
|
|
ct_mint: ConfidentialTransferMint,
|
|
ct_mint_authority: Keypair,
|
|
#[allow(dead_code)]
|
|
ct_mint_transfer_auditor_encryption_keypair: ElGamalKeypair,
|
|
ct_mint_withdraw_withheld_authority_encryption_keypair: ElGamalKeypair,
|
|
}
|
|
|
|
impl ConfidentialTransferMintWithKeypairs {
|
|
fn new() -> Self {
|
|
let ct_mint_authority = Keypair::new();
|
|
let ct_mint_transfer_auditor_encryption_keypair = ElGamalKeypair::new_rand();
|
|
let ct_mint_withdraw_withheld_authority_encryption_keypair = ElGamalKeypair::new_rand();
|
|
let ct_mint = ConfidentialTransferMint {
|
|
authority: ct_mint_authority.pubkey().into(),
|
|
auto_approve_new_accounts: true.into(),
|
|
auditor_encryption_pubkey: ct_mint_transfer_auditor_encryption_keypair.public.into(),
|
|
withdraw_withheld_authority_encryption_pubkey:
|
|
ct_mint_withdraw_withheld_authority_encryption_keypair
|
|
.public
|
|
.into(),
|
|
withheld_amount: EncryptedWithheldAmount::zeroed(),
|
|
};
|
|
Self {
|
|
ct_mint,
|
|
ct_mint_authority,
|
|
ct_mint_transfer_auditor_encryption_keypair,
|
|
ct_mint_withdraw_withheld_authority_encryption_keypair,
|
|
}
|
|
}
|
|
|
|
fn without_auto_approve() -> Self {
|
|
let mut x = Self::new();
|
|
x.ct_mint.auto_approve_new_accounts = false.into();
|
|
x
|
|
}
|
|
}
|
|
|
|
struct ConfidentialTokenAccountMeta {
|
|
token_account: Pubkey,
|
|
elgamal_keypair: ElGamalKeypair,
|
|
ae_key: AeKey,
|
|
}
|
|
|
|
impl ConfidentialTokenAccountMeta {
|
|
async fn new<T>(token: &Token<T, Keypair>, owner: &Keypair) -> Self
|
|
where
|
|
T: SendTransaction,
|
|
{
|
|
let token_account = token
|
|
.create_auxiliary_token_account_with_extension_space(
|
|
&Keypair::new(),
|
|
&owner.pubkey(),
|
|
vec![ExtensionType::ConfidentialTransferAccount],
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let (elgamal_keypair, ae_key) = token
|
|
.confidential_transfer_configure_token_account_and_keypairs(
|
|
&token_account,
|
|
owner,
|
|
TEST_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
Self {
|
|
token_account,
|
|
elgamal_keypair,
|
|
ae_key,
|
|
}
|
|
}
|
|
|
|
async fn with_tokens<T>(
|
|
token: &Token<T, Keypair>,
|
|
owner: &Keypair,
|
|
mint_authority: &Keypair,
|
|
amount: u64,
|
|
decimals: u8,
|
|
) -> Self
|
|
where
|
|
T: SendTransaction,
|
|
{
|
|
let meta = Self::new(token, owner).await;
|
|
|
|
token
|
|
.mint_to(&meta.token_account, mint_authority, amount)
|
|
.await
|
|
.unwrap();
|
|
|
|
token
|
|
.confidential_transfer_deposit(
|
|
&meta.token_account,
|
|
&meta.token_account,
|
|
owner,
|
|
amount,
|
|
decimals,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
token
|
|
.confidential_transfer_apply_pending_balance(
|
|
&meta.token_account,
|
|
owner,
|
|
1,
|
|
meta.ae_key.encrypt(amount),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
meta
|
|
}
|
|
|
|
async fn check_balances<T>(
|
|
&self,
|
|
token: &Token<T, Keypair>,
|
|
expected: ConfidentialTokenAccountBalances,
|
|
) where
|
|
T: SendTransaction,
|
|
{
|
|
let state = token.get_account_info(&self.token_account).await.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
extension
|
|
.pending_balance_lo
|
|
.decrypt(&self.elgamal_keypair.secret)
|
|
.unwrap(),
|
|
expected.pending_balance_lo,
|
|
);
|
|
assert_eq!(
|
|
extension
|
|
.pending_balance_hi
|
|
.decrypt(&self.elgamal_keypair.secret)
|
|
.unwrap(),
|
|
expected.pending_balance_hi,
|
|
);
|
|
assert_eq!(
|
|
extension
|
|
.available_balance
|
|
.decrypt(&self.elgamal_keypair.secret)
|
|
.unwrap(),
|
|
expected.available_balance,
|
|
);
|
|
assert_eq!(
|
|
self.ae_key
|
|
.decrypt(&extension.decryptable_available_balance.try_into().unwrap())
|
|
.unwrap(),
|
|
expected.decryptable_available_balance,
|
|
);
|
|
}
|
|
}
|
|
|
|
struct ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: u64,
|
|
pending_balance_hi: u64,
|
|
available_balance: u64,
|
|
decryptable_available_balance: u64,
|
|
}
|
|
|
|
async fn check_withheld_amount_in_mint<T>(
|
|
token: &Token<T, Keypair>,
|
|
withdraw_withheld_authority_encryption_keypair: &ElGamalKeypair,
|
|
expected: u64,
|
|
) where
|
|
T: SendTransaction,
|
|
{
|
|
let state = token.get_mint_info().await.unwrap();
|
|
let extension = state.get_extension::<ConfidentialTransferMint>().unwrap();
|
|
let decrypted_amount = extension
|
|
.withheld_amount
|
|
.decrypt(&withdraw_withheld_authority_encryption_keypair.secret)
|
|
.unwrap();
|
|
assert_eq!(decrypted_amount, expected);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_initialize_and_update_mint() {
|
|
let wrong_keypair = Keypair::new();
|
|
|
|
let ConfidentialTransferMintWithKeypairs {
|
|
ct_mint,
|
|
ct_mint_authority,
|
|
..
|
|
} = ConfidentialTransferMintWithKeypairs::new();
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext { token, .. } = context.token_context.unwrap();
|
|
|
|
let state = token.get_mint_info().await.unwrap();
|
|
let extension = state.get_extension::<ConfidentialTransferMint>().unwrap();
|
|
assert_eq!(*extension, ct_mint);
|
|
|
|
// Change the authority
|
|
let new_ct_mint_authority = Keypair::new();
|
|
let new_ct_mint = ConfidentialTransferMint {
|
|
authority: new_ct_mint_authority.pubkey(),
|
|
..ConfidentialTransferMint::default()
|
|
};
|
|
|
|
let err = token
|
|
.confidential_transfer_update_mint(
|
|
&wrong_keypair,
|
|
new_ct_mint,
|
|
Some(&new_ct_mint_authority),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err,
|
|
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
|
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
|
|
)))
|
|
);
|
|
token
|
|
.confidential_transfer_update_mint(
|
|
&ct_mint_authority,
|
|
new_ct_mint,
|
|
Some(&new_ct_mint_authority),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token.get_mint_info().await.unwrap();
|
|
let extension = state.get_extension::<ConfidentialTransferMint>().unwrap();
|
|
assert_eq!(*extension, new_ct_mint);
|
|
|
|
// Clear the authority
|
|
let new_ct_mint = ConfidentialTransferMint::default();
|
|
token
|
|
.confidential_transfer_update_mint(&new_ct_mint_authority, new_ct_mint, None)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token.get_mint_info().await.unwrap();
|
|
let extension = state.get_extension::<ConfidentialTransferMint>().unwrap();
|
|
assert_eq!(*extension, new_ct_mint);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_configure_token_account() {
|
|
let ConfidentialTransferMintWithKeypairs {
|
|
ct_mint,
|
|
ct_mint_authority,
|
|
..
|
|
} = ConfidentialTransferMintWithKeypairs::without_auto_approve();
|
|
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext { token, alice, .. } = context.token_context.unwrap();
|
|
let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await;
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert!(!bool::from(&extension.approved));
|
|
assert!(bool::from(&extension.allow_balance_credits));
|
|
assert_eq!(
|
|
extension.encryption_pubkey,
|
|
alice_meta.elgamal_keypair.public.into()
|
|
);
|
|
assert_eq!(
|
|
alice_meta
|
|
.ae_key
|
|
.decrypt(&(extension.decryptable_available_balance.try_into().unwrap()))
|
|
.unwrap(),
|
|
0
|
|
);
|
|
|
|
token
|
|
.confidential_transfer_approve_account(&alice_meta.token_account, &ct_mint_authority)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert!(bool::from(&extension.approved));
|
|
|
|
// Configuring an already initialized account should produce an error
|
|
let err = token
|
|
.confidential_transfer_configure_token_account(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
alice_meta.elgamal_keypair.public,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
TEST_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER,
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert_eq!(
|
|
err,
|
|
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
|
TransactionError::InstructionError(
|
|
0,
|
|
InstructionError::Custom(TokenError::ExtensionAlreadyInitialized as u32),
|
|
)
|
|
)))
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_enable_disable_balance_credits() {
|
|
let ConfidentialTransferMintWithKeypairs { ct_mint, .. } =
|
|
ConfidentialTransferMintWithKeypairs::new();
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext { token, alice, .. } = context.token_context.unwrap();
|
|
let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await;
|
|
|
|
token
|
|
.confidential_transfer_disable_balance_credits(&alice_meta.token_account, &alice)
|
|
.await
|
|
.unwrap();
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert!(!bool::from(&extension.allow_balance_credits));
|
|
|
|
token
|
|
.confidential_transfer_enable_balance_credits(&alice_meta.token_account, &alice)
|
|
.await
|
|
.unwrap();
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert!(bool::from(&extension.allow_balance_credits));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_new_account_is_empty() {
|
|
let ConfidentialTransferMintWithKeypairs { ct_mint, .. } =
|
|
ConfidentialTransferMintWithKeypairs::new();
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext { token, alice, .. } = context.token_context.unwrap();
|
|
|
|
let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await;
|
|
token
|
|
.confidential_transfer_empty_account(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
&alice_meta.elgamal_keypair,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_deposit() {
|
|
let ConfidentialTransferMintWithKeypairs { ct_mint, .. } =
|
|
ConfidentialTransferMintWithKeypairs::new();
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext {
|
|
token,
|
|
alice,
|
|
mint_authority,
|
|
decimals,
|
|
..
|
|
} = context.token_context.unwrap();
|
|
let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await;
|
|
|
|
token
|
|
.mint_to(&alice_meta.token_account, &mint_authority, 65537)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(state.base.amount, 65537);
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert_eq!(extension.pending_balance_credit_counter, 0.into());
|
|
assert_eq!(extension.expected_pending_balance_credit_counter, 0.into());
|
|
assert_eq!(extension.actual_pending_balance_credit_counter, 0.into());
|
|
assert_eq!(
|
|
extension.pending_balance_lo,
|
|
zk_token_elgamal::pod::ElGamalCiphertext::zeroed()
|
|
);
|
|
assert_eq!(
|
|
extension.pending_balance_hi,
|
|
zk_token_elgamal::pod::ElGamalCiphertext::zeroed()
|
|
);
|
|
assert_eq!(
|
|
extension.available_balance,
|
|
zk_token_elgamal::pod::ElGamalCiphertext::zeroed()
|
|
);
|
|
|
|
token
|
|
.confidential_transfer_deposit(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
65537,
|
|
decimals,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(state.base.amount, 0);
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert_eq!(extension.pending_balance_credit_counter, 1.into());
|
|
assert_eq!(extension.expected_pending_balance_credit_counter, 0.into());
|
|
assert_eq!(extension.actual_pending_balance_credit_counter, 0.into());
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 1,
|
|
pending_balance_hi: 1,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_deposit(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
0,
|
|
decimals,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let err = token
|
|
.confidential_transfer_deposit(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
0,
|
|
decimals,
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert_eq!(
|
|
err,
|
|
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
|
TransactionError::InstructionError(
|
|
0,
|
|
InstructionError::Custom(
|
|
TokenError::MaximumPendingBalanceCreditCounterExceeded as u32
|
|
),
|
|
)
|
|
)))
|
|
);
|
|
|
|
let new_decryptable_available_balance = alice_meta.ae_key.encrypt(65537_u64);
|
|
token
|
|
.confidential_transfer_apply_pending_balance(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
2,
|
|
new_decryptable_available_balance.clone(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert_eq!(
|
|
extension.decryptable_available_balance,
|
|
new_decryptable_available_balance.into(),
|
|
);
|
|
assert_eq!(extension.pending_balance_credit_counter, 0.into());
|
|
assert_eq!(extension.expected_pending_balance_credit_counter, 2.into());
|
|
assert_eq!(extension.actual_pending_balance_credit_counter, 2.into());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_withdraw() {
|
|
let ConfidentialTransferMintWithKeypairs { ct_mint, .. } =
|
|
ConfidentialTransferMintWithKeypairs::new();
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext {
|
|
token,
|
|
alice,
|
|
mint_authority,
|
|
decimals,
|
|
..
|
|
} = context.token_context.unwrap();
|
|
|
|
let alice_meta =
|
|
ConfidentialTokenAccountMeta::with_tokens(&token, &alice, &mint_authority, 42, decimals)
|
|
.await;
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(state.base.amount, 0);
|
|
|
|
token
|
|
.confidential_transfer_withdraw(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
21,
|
|
decimals,
|
|
42,
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(42_u64 - 21),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(state.base.amount, 21);
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 21,
|
|
decryptable_available_balance: 21,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_withdraw(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
21,
|
|
decimals,
|
|
21,
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(state.base.amount, 42);
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_empty_account(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
&alice_meta.elgamal_keypair,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_transfer() {
|
|
let ConfidentialTransferMintWithKeypairs { ct_mint, .. } =
|
|
ConfidentialTransferMintWithKeypairs::new();
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext {
|
|
token,
|
|
alice,
|
|
bob,
|
|
mint_authority,
|
|
decimals,
|
|
..
|
|
} = context.token_context.unwrap();
|
|
let alice_meta =
|
|
ConfidentialTokenAccountMeta::with_tokens(&token, &alice, &mint_authority, 42, decimals)
|
|
.await;
|
|
let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob).await;
|
|
|
|
// Self-transfer of 0 tokens
|
|
token
|
|
.confidential_transfer_transfer(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
0, // amount
|
|
42, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(42_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 42,
|
|
decryptable_available_balance: 42,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
// Self-transfer of N tokens
|
|
token
|
|
.confidential_transfer_transfer(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
42, // amount
|
|
42, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 42,
|
|
pending_balance_hi: 0,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
let err = token
|
|
.confidential_transfer_transfer(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
0, // amount
|
|
0, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert_eq!(
|
|
err,
|
|
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
|
TransactionError::InstructionError(
|
|
1,
|
|
InstructionError::Custom(
|
|
TokenError::MaximumPendingBalanceCreditCounterExceeded as u32
|
|
),
|
|
)
|
|
)))
|
|
);
|
|
|
|
token
|
|
.confidential_transfer_apply_pending_balance(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
2,
|
|
alice_meta.ae_key.encrypt(42_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 42,
|
|
decryptable_available_balance: 42,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_transfer(
|
|
&alice_meta.token_account,
|
|
&bob_meta.token_account,
|
|
&alice,
|
|
42, // amount
|
|
42, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_empty_account(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
&alice_meta.elgamal_keypair,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let err = token
|
|
.confidential_transfer_empty_account(
|
|
&bob_meta.token_account,
|
|
&bob,
|
|
&bob_meta.elgamal_keypair,
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err,
|
|
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
|
TransactionError::InstructionError(1, InstructionError::InvalidAccountData)
|
|
)))
|
|
);
|
|
|
|
let state = token
|
|
.get_account_info(&bob_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
|
|
// TODO: verify bob_meta pending and available balance once syscall lands
|
|
assert_eq!(
|
|
bob_meta
|
|
.ae_key
|
|
.decrypt(&extension.decryptable_available_balance.try_into().unwrap()),
|
|
Some(0),
|
|
);
|
|
|
|
token
|
|
.confidential_transfer_apply_pending_balance(
|
|
&bob_meta.token_account,
|
|
&bob,
|
|
1,
|
|
bob_meta.ae_key.encrypt(42_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&bob_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
|
|
// TODO: verify bob_meta pending and available balance once syscall lands
|
|
assert_eq!(
|
|
bob_meta
|
|
.ae_key
|
|
.decrypt(&extension.decryptable_available_balance.try_into().unwrap()),
|
|
Some(42),
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_transfer_with_fee() {
|
|
let ConfidentialTransferMintWithKeypairs { ct_mint, .. } =
|
|
ConfidentialTransferMintWithKeypairs::new();
|
|
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::TransferFeeConfig {
|
|
transfer_fee_config_authority: Some(Pubkey::new_unique()),
|
|
withdraw_withheld_authority: Some(Pubkey::new_unique()),
|
|
transfer_fee_basis_points: TEST_FEE_BASIS_POINTS,
|
|
maximum_fee: TEST_MAXIMUM_FEE,
|
|
},
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext {
|
|
token,
|
|
alice,
|
|
bob,
|
|
mint_authority,
|
|
decimals,
|
|
..
|
|
} = context.token_context.unwrap();
|
|
|
|
let epoch_info = test_epoch_info();
|
|
|
|
let alice_meta =
|
|
ConfidentialTokenAccountMeta::with_tokens(&token, &alice, &mint_authority, 100, decimals)
|
|
.await;
|
|
let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob).await;
|
|
|
|
// Self-transfer of 0 tokens
|
|
token
|
|
.confidential_transfer_transfer_with_fee(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
0, // amount
|
|
100, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(100_u64),
|
|
&epoch_info,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 100,
|
|
decryptable_available_balance: 100,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
// Self-transfers does not incur a fee
|
|
token
|
|
.confidential_transfer_transfer_with_fee(
|
|
&alice_meta.token_account,
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
100, // amount
|
|
100, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
&epoch_info,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 100,
|
|
pending_balance_hi: 0,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_apply_pending_balance(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
2,
|
|
alice_meta.ae_key.encrypt(100_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 100,
|
|
decryptable_available_balance: 100,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_transfer_with_fee(
|
|
&alice_meta.token_account,
|
|
&bob_meta.token_account,
|
|
&alice,
|
|
100, // amount
|
|
100, // available balance
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
&epoch_info,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
let state = token
|
|
.get_account_info(&alice_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
assert_eq!(
|
|
alice_meta
|
|
.ae_key
|
|
.decrypt(&extension.decryptable_available_balance.try_into().unwrap()),
|
|
Some(0),
|
|
);
|
|
|
|
// Alice account cannot be closed since there are withheld fees from self-transfer
|
|
token
|
|
.confidential_transfer_empty_account(
|
|
&alice_meta.token_account,
|
|
&alice,
|
|
&alice_meta.elgamal_keypair,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let err = token
|
|
.confidential_transfer_empty_account(
|
|
&bob_meta.token_account,
|
|
&bob,
|
|
&bob_meta.elgamal_keypair,
|
|
)
|
|
.await
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err,
|
|
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
|
TransactionError::InstructionError(1, InstructionError::InvalidAccountData)
|
|
)))
|
|
);
|
|
|
|
let state = token
|
|
.get_account_info(&bob_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
|
|
// TODO: check pending and available balance once curve syscall lands
|
|
assert_eq!(
|
|
bob_meta
|
|
.ae_key
|
|
.decrypt(&extension.decryptable_available_balance.try_into().unwrap()),
|
|
Some(0),
|
|
);
|
|
|
|
token
|
|
.confidential_transfer_apply_pending_balance(
|
|
&bob_meta.token_account,
|
|
&bob,
|
|
1,
|
|
bob_meta.ae_key.encrypt(94_u64),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&bob_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
|
|
// TODO: check pending and available balance once curve syscall lands
|
|
assert_eq!(
|
|
bob_meta
|
|
.ae_key
|
|
.decrypt(&extension.decryptable_available_balance.try_into().unwrap()),
|
|
Some(94),
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ct_withdraw_withheld_tokens_from_mint() {
|
|
let ConfidentialTransferMintWithKeypairs {
|
|
ct_mint,
|
|
ct_mint_withdraw_withheld_authority_encryption_keypair,
|
|
..
|
|
} = ConfidentialTransferMintWithKeypairs::new();
|
|
|
|
let ct_mint_withdraw_withheld_authority = Keypair::new();
|
|
|
|
let mut context = TestContext::new().await;
|
|
context
|
|
.init_token_with_mint(vec![
|
|
ExtensionInitializationParams::TransferFeeConfig {
|
|
transfer_fee_config_authority: Some(Pubkey::new_unique()),
|
|
withdraw_withheld_authority: Some(ct_mint_withdraw_withheld_authority.pubkey()),
|
|
transfer_fee_basis_points: TEST_FEE_BASIS_POINTS,
|
|
maximum_fee: TEST_MAXIMUM_FEE,
|
|
},
|
|
ExtensionInitializationParams::ConfidentialTransferMint { ct_mint },
|
|
])
|
|
.await
|
|
.unwrap();
|
|
|
|
let TokenContext {
|
|
token,
|
|
alice,
|
|
bob,
|
|
mint_authority,
|
|
decimals,
|
|
..
|
|
} = context.token_context.unwrap();
|
|
|
|
let epoch_info = test_epoch_info();
|
|
|
|
let alice_meta =
|
|
ConfidentialTokenAccountMeta::with_tokens(&token, &alice, &mint_authority, 100, decimals)
|
|
.await;
|
|
let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob).await;
|
|
|
|
token
|
|
.confidential_transfer_withdraw_withheld_tokens_from_mint(
|
|
&ct_mint_withdraw_withheld_authority,
|
|
&ct_mint_withdraw_withheld_authority_encryption_keypair,
|
|
&alice_meta.token_account,
|
|
0_u64,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 0,
|
|
pending_balance_hi: 0,
|
|
available_balance: 100,
|
|
decryptable_available_balance: 100,
|
|
},
|
|
)
|
|
.await;
|
|
|
|
check_withheld_amount_in_mint(
|
|
&token,
|
|
&ct_mint_withdraw_withheld_authority_encryption_keypair,
|
|
0,
|
|
)
|
|
.await;
|
|
|
|
// Test fee is 2.5% so the withheld fees should be 3
|
|
token
|
|
.confidential_transfer_transfer_with_fee(
|
|
&alice_meta.token_account,
|
|
&bob_meta.token_account,
|
|
&alice,
|
|
100,
|
|
100,
|
|
&alice_meta.elgamal_keypair,
|
|
alice_meta.ae_key.encrypt(0_u64),
|
|
&epoch_info,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let state = token
|
|
.get_account_info(&bob_meta.token_account)
|
|
.await
|
|
.unwrap();
|
|
let extension = state
|
|
.get_extension::<ConfidentialTransferAccount>()
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
extension
|
|
.withheld_amount
|
|
.decrypt(&ct_mint_withdraw_withheld_authority_encryption_keypair.secret),
|
|
Some(3),
|
|
);
|
|
|
|
token
|
|
.confidential_transfer_harvest_withheld_tokens_to_mint(&[&bob_meta.token_account])
|
|
.await
|
|
.unwrap();
|
|
|
|
check_withheld_amount_in_mint(
|
|
&token,
|
|
&ct_mint_withdraw_withheld_authority_encryption_keypair,
|
|
3,
|
|
)
|
|
.await;
|
|
|
|
token
|
|
.confidential_transfer_withdraw_withheld_tokens_from_mint(
|
|
&ct_mint_withdraw_withheld_authority,
|
|
&ct_mint_withdraw_withheld_authority_encryption_keypair,
|
|
&alice_meta.token_account,
|
|
3_u64,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
alice_meta
|
|
.check_balances(
|
|
&token,
|
|
ConfidentialTokenAccountBalances {
|
|
pending_balance_lo: 3,
|
|
pending_balance_hi: 0,
|
|
available_balance: 0,
|
|
decryptable_available_balance: 0,
|
|
},
|
|
)
|
|
.await;
|
|
}
|