diff --git a/clients/rust-legacy/tests/confidential_mint_burn.rs b/clients/rust-legacy/tests/confidential_mint_burn.rs index 71c651f1a..5e1374550 100644 --- a/clients/rust-legacy/tests/confidential_mint_burn.rs +++ b/clients/rust-legacy/tests/confidential_mint_burn.rs @@ -1095,3 +1095,100 @@ async fn pause_confidential_mint_burn() { ))) ); } + +#[tokio::test] +async fn fail_close_mint_with_confidential_supply() { + 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 supply_elgamal_keypair = ElGamalKeypair::new_rand(); + let supply_elgamal_pubkey = (*supply_elgamal_keypair.pubkey()).into(); + let supply_aes_key = AeKey::new_rand(); + let decryptable_supply = supply_aes_key.encrypt(0).into(); + + let mut context = TestContext::new().await; + context + .init_token_with_mint(vec![ + ExtensionInitializationParams::MintCloseAuthority { + close_authority: Some(confidential_transfer_authority.pubkey()), + }, + ExtensionInitializationParams::ConfidentialTransferMint { + authority: Some(confidential_transfer_authority.pubkey()), + auto_approve_new_accounts, + auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), + }, + ExtensionInitializationParams::ConfidentialMintBurn { + supply_elgamal_pubkey, + decryptable_supply, + }, + ]) + .await + .unwrap(); + + let TokenContext { + token, + mint_authority, + alice, + .. + } = context.token_context.unwrap(); + + let alice_meta = ConfidentialTokenAccountMeta::new(&token, &alice).await; + let mint_amount = 100; + + // Mint confidential tokens to Alice. This increases the confidential supply. + token + .confidential_transfer_mint( + &mint_authority.pubkey(), + &alice_meta.token_account, + None, + None, + None, + mint_amount, + &supply_elgamal_keypair, + alice_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + &supply_aes_key, + None, + &[&mint_authority], + ) + .await + .unwrap(); + + // Apply pending balance to finalize the mint + token + .confidential_transfer_apply_pending_balance( + &alice_meta.token_account, + &alice.pubkey(), + None, + alice_meta.elgamal_keypair.secret(), + &alice_meta.aes_key, + &[&alice], + ) + .await + .unwrap(); + + // Attempt to close the mint. + // This should fail because the confidential supply is non-zero (100). + let err = token + .close_account( + token.get_address(), + &alice.pubkey(), + &confidential_transfer_authority.pubkey(), + &[&confidential_transfer_authority], + ) + .await + .unwrap_err(); + + // 4. Assert that the error is MintHasSupply + assert_eq!( + err, + TokenClientError::Client(Box::new(TransportError::TransactionError( + TransactionError::InstructionError( + 0, + InstructionError::Custom(TokenError::MintHasSupply as u32) + ) + ))) + ); +} diff --git a/interface/src/extension/confidential_mint_burn/mod.rs b/interface/src/extension/confidential_mint_burn/mod.rs index 75d255b13..b992c790b 100644 --- a/interface/src/extension/confidential_mint_burn/mod.rs +++ b/interface/src/extension/confidential_mint_burn/mod.rs @@ -1,6 +1,10 @@ use { - crate::extension::{Extension, ExtensionType}, + crate::{ + error::TokenError, + extension::{Extension, ExtensionType}, + }, bytemuck::{Pod, Zeroable}, + solana_program_error::ProgramResult, solana_zk_sdk::encryption::pod::{ auth_encryption::PodAeCiphertext, elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, @@ -27,3 +31,20 @@ pub struct ConfidentialMintBurn { impl Extension for ConfidentialMintBurn { const TYPE: ExtensionType = ExtensionType::ConfidentialMintBurn; } + +impl ConfidentialMintBurn { + /// Checks if the mint can be closed based on confidential supply state + /// + /// The check verifies that the encrypted supply is an identically zero + /// ElGamal ciphertext. In case the encrypted supply is zero, but not + /// an identically zero ciphertext, one must use the + /// `RotateSupplyElGamalPubkey` to update the supply ciphertext to an + /// identically zero ciphertext. + pub fn closable(&self) -> ProgramResult { + if self.confidential_supply == PodElGamalCiphertext::default() { + Ok(()) + } else { + Err(TokenError::MintHasSupply.into()) + } + } +} diff --git a/program/src/processor.rs b/program/src/processor.rs index d4d93b8ec..5729c50d3 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -1273,6 +1273,10 @@ impl Processor { if u64::from(mint.base.supply) != 0 { return Err(TokenError::MintHasSupply.into()); } + + if let Ok(confidential_mint_burn) = mint.get_extension::() { + confidential_mint_burn.closable()?; + } } else { return Err(ProgramError::UninitializedAccount); }