Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions clients/rust-legacy/tests/confidential_transfer_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,3 +1199,170 @@ async fn confidential_transfer_configure_token_account_with_fee_with_registry()
PodElGamalCiphertext::default(),
);
}

#[tokio::test]
async fn test_withdraw_withheld_tokens_with_max_pending_counter() {
let transfer_fee_authority = Keypair::new();
let withdraw_withheld_authority = Keypair::new();

let confidential_transfer_authority = Keypair::new();
let auto_approve_new_accounts = true;
let auditor_elgamal_keypair = ElGamalKeypair::new_rand();
let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into();

let confidential_transfer_fee_authority = Keypair::new();
let withdraw_withheld_authority_elgamal_keypair = ElGamalKeypair::new_rand();
let withdraw_withheld_authority_elgamal_pubkey =
(*withdraw_withheld_authority_elgamal_keypair.pubkey()).into();

let mut context = TestContext::new().await;
context
.init_token_with_mint(vec![
ExtensionInitializationParams::TransferFeeConfig {
transfer_fee_config_authority: Some(transfer_fee_authority.pubkey()),
withdraw_withheld_authority: Some(withdraw_withheld_authority.pubkey()),
transfer_fee_basis_points: TEST_FEE_BASIS_POINTS,
maximum_fee: TEST_MAXIMUM_FEE,
},
ExtensionInitializationParams::ConfidentialTransferMint {
authority: Some(confidential_transfer_authority.pubkey()),
auto_approve_new_accounts,
auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey),
},
ExtensionInitializationParams::ConfidentialTransferFeeConfig {
authority: Some(confidential_transfer_fee_authority.pubkey()),
withdraw_withheld_authority_elgamal_pubkey,
},
])
.await
.unwrap();

let TokenContext {
token,
alice,
bob,
mint_authority,
decimals,
..
} = context.token_context.unwrap();

let alice_meta =
ConfidentialTokenAccountMeta::new(&token, &alice, &mint_authority, 1000, decimals).await;
let bob_meta =
ConfidentialTokenAccountMeta::new(&token, &bob, &mint_authority, 0, decimals).await;

// Setup destination for withdrawal
// We manually configure to have a low max pending limit (1) to easily saturate it.
let fee_collector = Keypair::new();
let fee_collector_account = Keypair::new();
let extensions = vec![
ExtensionType::ConfidentialTransferAccount,
ExtensionType::ConfidentialTransferFeeAmount,
];
token
.create_auxiliary_token_account_with_extension_space(
&fee_collector_account,
&fee_collector.pubkey(),
extensions,
)
.await
.unwrap();

let fc_elgamal =
ElGamalKeypair::new_from_signer(&fee_collector, &fee_collector_account.pubkey().to_bytes())
.unwrap();
let fc_aes =
AeKey::new_from_signer(&fee_collector, &fee_collector_account.pubkey().to_bytes()).unwrap();

// Configure with max_pending_balance_credit_counter = 1
token
.confidential_transfer_configure_token_account(
&fee_collector_account.pubkey(),
&fee_collector.pubkey(),
None,
Some(1),
&fc_elgamal,
&fc_aes,
&[&fee_collector],
)
.await
.unwrap();

// Saturate destinaion pending counter
token
.mint_to(
&fee_collector_account.pubkey(),
&mint_authority.pubkey(),
100,
&[&mint_authority],
)
.await
.unwrap();
token
.confidential_transfer_deposit(
&fee_collector_account.pubkey(),
&fee_collector.pubkey(),
100,
decimals,
&[&fee_collector],
)
.await
.unwrap();

// Generate fees in Bob's account via a transfer from Alice
let transfer_fee_parameters = TransferFee {
epoch: 0.into(),
maximum_fee: TEST_MAXIMUM_FEE.into(),
transfer_fee_basis_points: TEST_FEE_BASIS_POINTS.into(),
};

token
.confidential_transfer_transfer_with_fee(
&alice_meta.token_account,
&bob_meta.token_account,
&alice.pubkey(),
None,
None,
None,
None,
None,
200, // Amount
None,
&alice_meta.elgamal_keypair,
&alice_meta.aes_key,
bob_meta.elgamal_keypair.pubkey(),
Some(auditor_elgamal_keypair.pubkey()),
withdraw_withheld_authority_elgamal_keypair.pubkey(),
transfer_fee_parameters.transfer_fee_basis_points.into(),
transfer_fee_parameters.maximum_fee.into(),
&[&alice],
)
.await
.unwrap();

// Calculate fee: 200 * 2.5% = 5
let fee = 5;
let new_decryptable_balance = fc_aes.encrypt(fee);

// Withdraw fees from Bob to destination account
token
.confidential_transfer_withdraw_withheld_tokens_from_accounts(
&fee_collector_account.pubkey(),
&withdraw_withheld_authority.pubkey(),
None,
None,
&withdraw_withheld_authority_elgamal_keypair,
fc_elgamal.pubkey(),
&new_decryptable_balance.into(),
&[&bob_meta.token_account],
&[&withdraw_withheld_authority],
)
.await
.unwrap();

let available = token
.confidential_transfer_get_available_balance(&fee_collector_account.pubkey(), &fc_aes)
.await
.unwrap();
assert_eq!(available, fee);
}
14 changes: 14 additions & 0 deletions interface/src/extension/confidential_transfer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,20 @@ impl ConfidentialTransferAccount {
Ok(())
}

/// Checks if a confidential extension is configured to receive withheld tokens.
///
/// Since withheld tokens are credited directly to the available balance,
/// we do not need to check the pending balance credit counter.
pub fn valid_as_withheld_amount_destination(&self) -> ProgramResult {
self.approved()?;

if !bool::from(self.allow_confidential_credits) {
return Err(TokenError::ConfidentialTransferDepositsAndTransfersDisabled.into());
}

Ok(())
}

/// Increments a confidential extension pending balance credit counter.
pub fn increment_pending_balance_credit_counter(&mut self) -> ProgramResult {
self.pending_balance_credit_counter = (u64::from(self.pending_balance_credit_counter)
Expand Down
4 changes: 2 additions & 2 deletions program/src/extension/confidential_transfer_fee/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn process_withdraw_withheld_tokens_from_mint(
}
let destination_confidential_transfer_account =
destination_account.get_extension_mut::<ConfidentialTransferAccount>()?;
destination_confidential_transfer_account.valid_as_destination()?;
destination_confidential_transfer_account.valid_as_withheld_amount_destination()?;

// The funds are moved from the mint to a destination account. Here, the
// `source` equates to the withdraw withheld authority associated in the
Expand Down Expand Up @@ -228,7 +228,7 @@ fn process_withdraw_withheld_tokens_from_accounts(
{
let destination_confidential_transfer_account =
destination_account.get_extension::<ConfidentialTransferAccount>()?;
destination_confidential_transfer_account.valid_as_destination()?;
destination_confidential_transfer_account.valid_as_withheld_amount_destination()?;

// The funds are moved from the accounts to a destination account. Here, the
// `source` equates to the withdraw withheld authority associated in the
Expand Down