1use crate::account::AccountView;
24use crate::address::Address;
25use crate::error::ProgramError;
26use crate::instruction::{InstructionAccount, Signer};
27use crate::token::{
28 authority_meta, require_authority_signed_direct, require_multisig_signers_direct, Invoke,
29 TokenInstruction, TokenSink, Trailing,
30};
31use crate::ProgramResult;
32use core::num::NonZeroI8;
33
34pub const IX_CONFIDENTIAL_TRANSFER: u8 = 27;
37
38pub const DECRYPTABLE_BALANCE_LEN: usize = 36;
41pub const ELGAMAL_CIPHERTEXT_LEN: usize = 64;
43pub const ELGAMAL_PUBKEY_LEN: usize = 32;
45
46pub type DecryptableBalance = [u8; DECRYPTABLE_BALANCE_LEN];
48pub type ElGamalCiphertext = [u8; ELGAMAL_CIPHERTEXT_LEN];
50pub type ElGamalPubkey = [u8; ELGAMAL_PUBKEY_LEN];
52
53#[derive(Clone, Copy)]
55pub enum ProofLocation<'a> {
56 InstructionOffset(NonZeroI8),
59 ContextStateAccount(&'a AccountView<'a>),
61}
62
63impl ProofLocation<'_> {
64 #[inline(always)]
67 pub const fn offset_byte(&self) -> u8 {
68 match self {
69 Self::InstructionOffset(offset) => offset.get() as u8,
70 Self::ContextStateAccount(_) => 0,
71 }
72 }
73
74 #[inline(always)]
75 const fn is_offset(&self) -> bool {
76 matches!(self, Self::InstructionOffset(_))
77 }
78}
79
80struct ProofAccounts<'a> {
84 views: [Option<&'a AccountView<'a>>; 6],
85 len: usize,
86}
87
88impl<'a> ProofAccounts<'a> {
89 #[inline(always)]
90 fn gather<'x: 'a>(
91 instructions_sysvar: Option<&'x AccountView<'x>>,
92 proofs: &[ProofLocation<'x>],
93 ) -> Result<Self, ProgramError> {
94 let mut out = Self {
95 views: [None; 6],
96 len: 0,
97 };
98 if proofs.iter().any(ProofLocation::is_offset) {
99 let sysvar = instructions_sysvar.ok_or(ProgramError::NotEnoughAccountKeys)?;
102 out.views[0] = Some(sysvar);
103 out.len = 1;
104 }
105 for proof in proofs {
106 if let ProofLocation::ContextStateAccount(account) = proof {
107 out.views[out.len] = Some(*account);
108 out.len += 1;
109 }
110 }
111 Ok(out)
112 }
113
114 #[inline(always)]
116 fn dense(&self, fallback: &'a AccountView<'a>) -> ([&'a AccountView<'a>; 6], usize) {
117 let mut dense = [fallback; 6];
118 let mut i = 0;
119 while i < self.len {
120 if let Some(view) = self.views[i] {
121 dense[i] = view;
122 }
123 i += 1;
124 }
125 (dense, self.len)
126 }
127}
128
129pub mod encoders {
131 use super::*;
132
133 #[inline(always)]
134 fn nullable_32(out: &mut [u8], value: Option<&[u8; 32]>) -> ProgramResult {
135 match value {
136 Some(value) if value == &[0u8; 32] => Err(ProgramError::InvalidArgument),
137 Some(value) => {
138 out.copy_from_slice(value);
139 Ok(())
140 }
141 None => Ok(()),
142 }
143 }
144
145 #[inline(always)]
147 pub fn encode_initialize_mint(
148 authority: Option<&Address>,
149 auto_approve_new_accounts: bool,
150 auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
151 ) -> Result<[u8; 67], ProgramError> {
152 let mut data = [0u8; 67];
153 data[0] = IX_CONFIDENTIAL_TRANSFER;
154 data[1] = 0;
155 nullable_32(&mut data[2..34], authority.map(|a| a.as_array()))?;
156 data[34] = u8::from(auto_approve_new_accounts);
157 nullable_32(&mut data[35..67], auditor_elgamal_pubkey)?;
158 Ok(data)
159 }
160
161 #[inline(always)]
163 pub fn encode_update_mint(
164 auto_approve_new_accounts: bool,
165 auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
166 ) -> Result<[u8; 35], ProgramError> {
167 let mut data = [0u8; 35];
168 data[0] = IX_CONFIDENTIAL_TRANSFER;
169 data[1] = 1;
170 data[2] = u8::from(auto_approve_new_accounts);
171 nullable_32(&mut data[3..35], auditor_elgamal_pubkey)?;
172 Ok(data)
173 }
174
175 #[inline(always)]
177 pub fn encode_configure_account(
178 decryptable_zero_balance: &DecryptableBalance,
179 maximum_pending_balance_credit_counter: u64,
180 proof_offset: u8,
181 ) -> [u8; 47] {
182 let mut data = [0u8; 47];
183 data[0] = IX_CONFIDENTIAL_TRANSFER;
184 data[1] = 2;
185 data[2..38].copy_from_slice(decryptable_zero_balance);
186 data[38..46].copy_from_slice(&maximum_pending_balance_credit_counter.to_le_bytes());
187 data[46] = proof_offset;
188 data
189 }
190
191 #[inline(always)]
194 pub const fn encode_bare(sub: u8) -> [u8; 2] {
195 [IX_CONFIDENTIAL_TRANSFER, sub]
196 }
197
198 #[inline(always)]
200 pub const fn encode_empty_account(proof_offset: u8) -> [u8; 3] {
201 [IX_CONFIDENTIAL_TRANSFER, 4, proof_offset]
202 }
203
204 #[inline(always)]
206 pub fn encode_deposit(amount: u64, decimals: u8) -> [u8; 11] {
207 let mut data = [0u8; 11];
208 data[0] = IX_CONFIDENTIAL_TRANSFER;
209 data[1] = 5;
210 data[2..10].copy_from_slice(&amount.to_le_bytes());
211 data[10] = decimals;
212 data
213 }
214
215 #[inline(always)]
217 pub fn encode_withdraw(
218 amount: u64,
219 decimals: u8,
220 new_decryptable_available_balance: &DecryptableBalance,
221 equality_proof_offset: u8,
222 range_proof_offset: u8,
223 ) -> [u8; 49] {
224 let mut data = [0u8; 49];
225 data[0] = IX_CONFIDENTIAL_TRANSFER;
226 data[1] = 6;
227 data[2..10].copy_from_slice(&amount.to_le_bytes());
228 data[10] = decimals;
229 data[11..47].copy_from_slice(new_decryptable_available_balance);
230 data[47] = equality_proof_offset;
231 data[48] = range_proof_offset;
232 data
233 }
234
235 #[inline(always)]
239 pub fn encode_transfer<const N: usize>(
240 sub: u8,
241 new_source_decryptable_available_balance: &DecryptableBalance,
242 transfer_amount_auditor_ciphertext_lo: &ElGamalCiphertext,
243 transfer_amount_auditor_ciphertext_hi: &ElGamalCiphertext,
244 proof_offsets: [u8; N],
245 ) -> ([u8; 171], usize) {
246 let mut data = [0u8; 171];
247 data[0] = IX_CONFIDENTIAL_TRANSFER;
248 data[1] = sub;
249 data[2..38].copy_from_slice(new_source_decryptable_available_balance);
250 data[38..102].copy_from_slice(transfer_amount_auditor_ciphertext_lo);
251 data[102..166].copy_from_slice(transfer_amount_auditor_ciphertext_hi);
252 let mut i = 0;
253 while i < N && i < 5 {
254 data[166 + i] = proof_offsets[i];
255 i += 1;
256 }
257 (data, 166 + i)
258 }
259
260 #[inline(always)]
262 pub fn encode_apply_pending_balance(
263 expected_pending_balance_credit_counter: u64,
264 new_decryptable_available_balance: &DecryptableBalance,
265 ) -> [u8; 46] {
266 let mut data = [0u8; 46];
267 data[0] = IX_CONFIDENTIAL_TRANSFER;
268 data[1] = 8;
269 data[2..10].copy_from_slice(&expected_pending_balance_credit_counter.to_le_bytes());
270 data[10..46].copy_from_slice(new_decryptable_available_balance);
271 data
272 }
273}
274
275use encoders::*;
276
277macro_rules! confidential_methods {
279 ($name:ident, authority = $auth:ident) => {
280 impl $name<'_> {
281 #[inline]
283 pub fn invoke(&self) -> ProgramResult {
284 require_authority_signed_direct(self.$auth)?;
285 self.emit(&[], &mut Invoke::token_2022(&[]))
286 }
287
288 #[inline]
290 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
291 self.emit(&[], &mut Invoke::token_2022(signers))
292 }
293
294 #[inline]
297 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
298 require_multisig_signers_direct(multisig_signers)?;
299 self.emit(multisig_signers, &mut Invoke::token_2022(&[]))
300 }
301
302 #[inline]
305 pub fn invoke_signed_multisig(
306 &self,
307 multisig_signers: &[&AccountView<'_>],
308 signers: &[Signer<'_, '_>],
309 ) -> ProgramResult {
310 self.emit(multisig_signers, &mut Invoke::token_2022(signers))
311 }
312 }
313 };
314}
315
316pub struct InitializeConfidentialTransferMint<'a> {
322 pub mint: &'a AccountView<'a>,
323 pub authority: Option<&'a Address>,
324 pub auto_approve_new_accounts: bool,
325 pub auditor_elgamal_pubkey: Option<&'a ElGamalPubkey>,
326}
327
328impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeConfidentialTransferMint<'x> {
329 #[inline(always)]
330 fn emit(
331 &self,
332 _multisig_signers: &[&'a AccountView<'a>],
333 sink: &mut impl TokenSink<'a>,
334 ) -> ProgramResult {
335 let data = encode_initialize_mint(
336 self.authority,
337 self.auto_approve_new_accounts,
338 self.auditor_elgamal_pubkey,
339 )?;
340 let accounts = [InstructionAccount::writable(self.mint.address())];
341 let views = [self.mint];
342 sink.emit(&data, accounts, views, &[])
343 }
344}
345
346impl InitializeConfidentialTransferMint<'_> {
347 #[inline]
349 pub fn invoke(&self) -> ProgramResult {
350 self.emit(&[], &mut Invoke::token_2022(&[]))
351 }
352}
353
354pub struct UpdateConfidentialTransferMint<'a> {
356 pub mint: &'a AccountView<'a>,
357 pub authority: &'a AccountView<'a>,
358 pub auto_approve_new_accounts: bool,
359 pub auditor_elgamal_pubkey: Option<&'a ElGamalPubkey>,
360}
361
362impl<'a, 'x: 'a> TokenInstruction<'a> for UpdateConfidentialTransferMint<'x> {
363 #[inline(always)]
364 fn emit(
365 &self,
366 multisig_signers: &[&'a AccountView<'a>],
367 sink: &mut impl TokenSink<'a>,
368 ) -> ProgramResult {
369 let data = encode_update_mint(self.auto_approve_new_accounts, self.auditor_elgamal_pubkey)?;
370 let accounts = [
371 InstructionAccount::writable(self.mint.address()),
372 authority_meta(self.authority, multisig_signers),
373 ];
374 let views = [self.mint, self.authority];
375 sink.emit(
376 &data,
377 accounts,
378 views,
379 &[Trailing::signers(multisig_signers)],
380 )
381 }
382}
383
384confidential_methods!(UpdateConfidentialTransferMint, authority = authority);
385
386macro_rules! account_instruction {
392 ($(#[$doc:meta])* $name:ident { account = $account:ident $(, @mint $mint:ident)? $(, $field:ident : $ty:ty)* $(,)? } data = |$s:ident| $data:expr;) => {
393 $(#[$doc])*
394 pub struct $name<'a> {
395 pub $account: &'a AccountView<'a>,
396 $(pub $mint: &'a AccountView<'a>,)?
397 pub authority: &'a AccountView<'a>,
398 $(pub $field: $ty,)*
399 }
400
401 impl<'a, 'x: 'a> TokenInstruction<'a> for $name<'x> {
402 #[inline(always)]
403 fn emit(
404 &self,
405 multisig_signers: &[&'a AccountView<'a>],
406 sink: &mut impl TokenSink<'a>,
407 ) -> ProgramResult {
408 let $s = self;
409 let data = $data;
410 let accounts = [
411 InstructionAccount::writable(self.$account.address()),
412 $(InstructionAccount::readonly(self.$mint.address()),)?
413 authority_meta(self.authority, multisig_signers),
414 ];
415 let views = [self.$account, $(self.$mint,)? self.authority];
416 sink.emit(&data, accounts, views, &[Trailing::signers(multisig_signers)])
417 }
418 }
419
420 confidential_methods!($name, authority = authority);
421 };
422}
423
424account_instruction! {
425 ApproveConfidentialAccount { account = account, @mint mint }
428 data = |_s| encode_bare(3);
429}
430
431account_instruction! {
432 ConfidentialDeposit { account = account, @mint mint, amount: u64, decimals: u8 }
435 data = |s| encode_deposit(s.amount, s.decimals);
436}
437
438account_instruction! {
439 ApplyPendingConfidentialBalance {
442 account = account,
443 expected_pending_balance_credit_counter: u64,
444 new_decryptable_available_balance: &'a DecryptableBalance,
445 }
446 data = |s| encode_apply_pending_balance(
447 s.expected_pending_balance_credit_counter,
448 s.new_decryptable_available_balance,
449 );
450}
451
452account_instruction! {
453 EnableConfidentialCredits { account = account }
455 data = |_s| encode_bare(9);
456}
457
458account_instruction! {
459 DisableConfidentialCredits { account = account }
461 data = |_s| encode_bare(10);
462}
463
464account_instruction! {
465 EnableNonConfidentialCredits { account = account }
467 data = |_s| encode_bare(11);
468}
469
470account_instruction! {
471 DisableNonConfidentialCredits { account = account }
473 data = |_s| encode_bare(12);
474}
475
476pub struct ConfigureConfidentialAccountWithRegistry<'a> {
480 pub account: &'a AccountView<'a>,
481 pub mint: &'a AccountView<'a>,
482 pub elgamal_registry: &'a AccountView<'a>,
483 pub payer: Option<(&'a AccountView<'a>, &'a AccountView<'a>)>,
486}
487
488impl<'a, 'x: 'a> TokenInstruction<'a> for ConfigureConfidentialAccountWithRegistry<'x> {
489 #[inline(always)]
490 fn emit(
491 &self,
492 _multisig_signers: &[&'a AccountView<'a>],
493 sink: &mut impl TokenSink<'a>,
494 ) -> ProgramResult {
495 let data = encode_bare(14);
496 let accounts = [
497 InstructionAccount::writable(self.account.address()),
498 InstructionAccount::readonly(self.mint.address()),
499 InstructionAccount::readonly(self.elgamal_registry.address()),
500 ];
501 let views = [self.account, self.mint, self.elgamal_registry];
502 match self.payer {
503 Some((payer, system_program)) => sink.emit(
504 &data,
505 accounts,
506 views,
507 &[
508 Trailing {
509 views: &[payer],
510 writable: true,
511 signer: true,
512 },
513 Trailing::readonly(&[system_program]),
514 ],
515 ),
516 None => sink.emit(&data, accounts, views, &[]),
517 }
518 }
519}
520
521impl ConfigureConfidentialAccountWithRegistry<'_> {
522 #[inline]
524 pub fn invoke(&self) -> ProgramResult {
525 self.emit(&[], &mut Invoke::token_2022(&[]))
526 }
527
528 #[inline]
530 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
531 self.emit(&[], &mut Invoke::token_2022(signers))
532 }
533}
534
535#[allow(clippy::too_many_arguments)]
542#[inline(always)]
543fn emit_with_proofs<'a, 'x: 'a, const N: usize>(
544 sink: &mut impl TokenSink<'a>,
545 data: &[u8],
546 accounts: [InstructionAccount<'a>; N],
547 views: [&'a AccountView<'a>; N],
548 instructions_sysvar: Option<&'x AccountView<'x>>,
549 proofs: &[ProofLocation<'x>],
550 authority: &'x AccountView<'x>,
551 multisig_signers: &[&'a AccountView<'a>],
552) -> ProgramResult {
553 let gathered = ProofAccounts::gather(instructions_sysvar, proofs)?;
554 let (dense, len) = gathered.dense(authority);
555 let authority_run: [&'a AccountView<'a>; 1] = [authority];
556 sink.emit(
557 data,
558 accounts,
559 views,
560 &[
561 Trailing::readonly(&dense[..len]),
562 Trailing {
563 views: &authority_run,
564 writable: false,
565 signer: multisig_signers.is_empty(),
566 },
567 Trailing::signers(multisig_signers),
568 ],
569 )
570}
571
572pub struct ConfigureConfidentialAccount<'a> {
575 pub account: &'a AccountView<'a>,
576 pub mint: &'a AccountView<'a>,
577 pub authority: &'a AccountView<'a>,
578 pub decryptable_zero_balance: &'a DecryptableBalance,
579 pub maximum_pending_balance_credit_counter: u64,
580 pub proof: ProofLocation<'a>,
581 pub instructions_sysvar: Option<&'a AccountView<'a>>,
583}
584
585impl<'a, 'x: 'a> TokenInstruction<'a> for ConfigureConfidentialAccount<'x> {
586 #[inline(always)]
587 fn emit(
588 &self,
589 multisig_signers: &[&'a AccountView<'a>],
590 sink: &mut impl TokenSink<'a>,
591 ) -> ProgramResult {
592 let data = encode_configure_account(
593 self.decryptable_zero_balance,
594 self.maximum_pending_balance_credit_counter,
595 self.proof.offset_byte(),
596 );
597 let accounts = [
598 InstructionAccount::writable(self.account.address()),
599 InstructionAccount::readonly(self.mint.address()),
600 ];
601 emit_with_proofs(
602 sink,
603 &data,
604 accounts,
605 [self.account, self.mint],
606 self.instructions_sysvar,
607 &[self.proof],
608 self.authority,
609 multisig_signers,
610 )
611 }
612}
613
614confidential_methods!(ConfigureConfidentialAccount, authority = authority);
615
616pub struct EmptyConfidentialAccount<'a> {
619 pub account: &'a AccountView<'a>,
620 pub authority: &'a AccountView<'a>,
621 pub proof: ProofLocation<'a>,
622 pub instructions_sysvar: Option<&'a AccountView<'a>>,
624}
625
626impl<'a, 'x: 'a> TokenInstruction<'a> for EmptyConfidentialAccount<'x> {
627 #[inline(always)]
628 fn emit(
629 &self,
630 multisig_signers: &[&'a AccountView<'a>],
631 sink: &mut impl TokenSink<'a>,
632 ) -> ProgramResult {
633 let data = encode_empty_account(self.proof.offset_byte());
634 let accounts = [InstructionAccount::writable(self.account.address())];
635 emit_with_proofs(
636 sink,
637 &data,
638 accounts,
639 [self.account],
640 self.instructions_sysvar,
641 &[self.proof],
642 self.authority,
643 multisig_signers,
644 )
645 }
646}
647
648confidential_methods!(EmptyConfidentialAccount, authority = authority);
649
650pub struct ConfidentialWithdraw<'a> {
653 pub account: &'a AccountView<'a>,
654 pub mint: &'a AccountView<'a>,
655 pub authority: &'a AccountView<'a>,
656 pub amount: u64,
657 pub decimals: u8,
658 pub new_decryptable_available_balance: &'a DecryptableBalance,
659 pub equality_proof: ProofLocation<'a>,
660 pub range_proof: ProofLocation<'a>,
661 pub instructions_sysvar: Option<&'a AccountView<'a>>,
663}
664
665impl<'a, 'x: 'a> TokenInstruction<'a> for ConfidentialWithdraw<'x> {
666 #[inline(always)]
667 fn emit(
668 &self,
669 multisig_signers: &[&'a AccountView<'a>],
670 sink: &mut impl TokenSink<'a>,
671 ) -> ProgramResult {
672 let data = encode_withdraw(
673 self.amount,
674 self.decimals,
675 self.new_decryptable_available_balance,
676 self.equality_proof.offset_byte(),
677 self.range_proof.offset_byte(),
678 );
679 let accounts = [
680 InstructionAccount::writable(self.account.address()),
681 InstructionAccount::readonly(self.mint.address()),
682 ];
683 emit_with_proofs(
684 sink,
685 &data,
686 accounts,
687 [self.account, self.mint],
688 self.instructions_sysvar,
689 &[self.equality_proof, self.range_proof],
690 self.authority,
691 multisig_signers,
692 )
693 }
694}
695
696confidential_methods!(ConfidentialWithdraw, authority = authority);
697
698pub struct ConfidentialTransfer<'a> {
701 pub source: &'a AccountView<'a>,
702 pub mint: &'a AccountView<'a>,
703 pub destination: &'a AccountView<'a>,
704 pub authority: &'a AccountView<'a>,
705 pub new_source_decryptable_available_balance: &'a DecryptableBalance,
706 pub transfer_amount_auditor_ciphertext_lo: &'a ElGamalCiphertext,
707 pub transfer_amount_auditor_ciphertext_hi: &'a ElGamalCiphertext,
708 pub equality_proof: ProofLocation<'a>,
709 pub ciphertext_validity_proof: ProofLocation<'a>,
710 pub range_proof: ProofLocation<'a>,
711 pub instructions_sysvar: Option<&'a AccountView<'a>>,
713}
714
715impl<'a, 'x: 'a> TokenInstruction<'a> for ConfidentialTransfer<'x> {
716 #[inline(always)]
717 fn emit(
718 &self,
719 multisig_signers: &[&'a AccountView<'a>],
720 sink: &mut impl TokenSink<'a>,
721 ) -> ProgramResult {
722 let (data, len) = encode_transfer(
723 7,
724 self.new_source_decryptable_available_balance,
725 self.transfer_amount_auditor_ciphertext_lo,
726 self.transfer_amount_auditor_ciphertext_hi,
727 [
728 self.equality_proof.offset_byte(),
729 self.ciphertext_validity_proof.offset_byte(),
730 self.range_proof.offset_byte(),
731 ],
732 );
733 let accounts = [
734 InstructionAccount::writable(self.source.address()),
735 InstructionAccount::readonly(self.mint.address()),
736 InstructionAccount::writable(self.destination.address()),
737 ];
738 emit_with_proofs(
739 sink,
740 &data[..len],
741 accounts,
742 [self.source, self.mint, self.destination],
743 self.instructions_sysvar,
744 &[
745 self.equality_proof,
746 self.ciphertext_validity_proof,
747 self.range_proof,
748 ],
749 self.authority,
750 multisig_signers,
751 )
752 }
753}
754
755confidential_methods!(ConfidentialTransfer, authority = authority);
756
757pub struct ConfidentialTransferWithFee<'a> {
761 pub source: &'a AccountView<'a>,
762 pub mint: &'a AccountView<'a>,
763 pub destination: &'a AccountView<'a>,
764 pub authority: &'a AccountView<'a>,
765 pub new_source_decryptable_available_balance: &'a DecryptableBalance,
766 pub transfer_amount_auditor_ciphertext_lo: &'a ElGamalCiphertext,
767 pub transfer_amount_auditor_ciphertext_hi: &'a ElGamalCiphertext,
768 pub equality_proof: ProofLocation<'a>,
769 pub transfer_amount_ciphertext_validity_proof: ProofLocation<'a>,
770 pub fee_sigma_proof: ProofLocation<'a>,
771 pub fee_ciphertext_validity_proof: ProofLocation<'a>,
772 pub range_proof: ProofLocation<'a>,
773 pub instructions_sysvar: Option<&'a AccountView<'a>>,
775}
776
777impl<'a, 'x: 'a> TokenInstruction<'a> for ConfidentialTransferWithFee<'x> {
778 #[inline(always)]
779 fn emit(
780 &self,
781 multisig_signers: &[&'a AccountView<'a>],
782 sink: &mut impl TokenSink<'a>,
783 ) -> ProgramResult {
784 let (data, len) = encode_transfer(
785 13,
786 self.new_source_decryptable_available_balance,
787 self.transfer_amount_auditor_ciphertext_lo,
788 self.transfer_amount_auditor_ciphertext_hi,
789 [
790 self.equality_proof.offset_byte(),
791 self.transfer_amount_ciphertext_validity_proof.offset_byte(),
792 self.fee_sigma_proof.offset_byte(),
793 self.fee_ciphertext_validity_proof.offset_byte(),
794 self.range_proof.offset_byte(),
795 ],
796 );
797 let accounts = [
798 InstructionAccount::writable(self.source.address()),
799 InstructionAccount::readonly(self.mint.address()),
800 InstructionAccount::writable(self.destination.address()),
801 ];
802 emit_with_proofs(
803 sink,
804 &data[..len],
805 accounts,
806 [self.source, self.mint, self.destination],
807 self.instructions_sysvar,
808 &[
809 self.equality_proof,
810 self.transfer_amount_ciphertext_validity_proof,
811 self.fee_sigma_proof,
812 self.fee_ciphertext_validity_proof,
813 self.range_proof,
814 ],
815 self.authority,
816 multisig_signers,
817 )
818 }
819}
820
821confidential_methods!(ConfidentialTransferWithFee, authority = authority);
822
823#[cfg(test)]
824mod tests {
825 use super::encoders::*;
826 use super::*;
827
828 #[test]
829 fn fixed_layouts_have_the_interface_sizes() {
830 assert_eq!(encode_bare(9), [27, 9]);
831 assert_eq!(encode_empty_account(3), [27, 4, 3]);
832 assert_eq!(encode_deposit(1, 6), [27, 5, 1, 0, 0, 0, 0, 0, 0, 0, 6]);
833 let balance = [9u8; DECRYPTABLE_BALANCE_LEN];
834 let configure = encode_configure_account(&balance, 65_536, 1);
835 assert_eq!(&configure[..2], &[27, 2]);
836 assert_eq!(&configure[2..38], &balance);
837 assert_eq!(&configure[38..46], &65_536u64.to_le_bytes());
838 assert_eq!(configure[46], 1);
839 let withdraw = encode_withdraw(5, 2, &balance, 0, 0xff);
840 assert_eq!(withdraw.len(), 49);
841 assert_eq!(&withdraw[47..], &[0, 0xff]);
842 let apply = encode_apply_pending_balance(4, &balance);
843 assert_eq!(&apply[2..10], &4u64.to_le_bytes());
844 let lo = [1u8; ELGAMAL_CIPHERTEXT_LEN];
845 let hi = [2u8; ELGAMAL_CIPHERTEXT_LEN];
846 let (transfer, len) = encode_transfer(7, &balance, &lo, &hi, [1, 2, 3]);
847 assert_eq!(len, 169);
848 assert_eq!(&transfer[166..169], &[1, 2, 3]);
849 let (with_fee, len) = encode_transfer(13, &balance, &lo, &hi, [1, 2, 3, 4, 5]);
850 assert_eq!(len, 171);
851 assert_eq!(&with_fee[166..171], &[1, 2, 3, 4, 5]);
852 }
853
854 #[test]
855 fn a_present_zero_key_is_refused_and_an_absent_one_is_zero() {
856 let key = [4u8; ELGAMAL_PUBKEY_LEN];
857 let authority = Address::new_from_array([3; 32]);
858 let data = encode_initialize_mint(Some(&authority), true, Some(&key)).unwrap();
859 assert_eq!(&data[2..34], &[3u8; 32]);
860 assert_eq!(data[34], 1);
861 assert_eq!(&data[35..], &key);
862 let data = encode_initialize_mint(None, false, None).unwrap();
863 assert_eq!(&data[2..], &[0u8; 65]);
864 assert!(encode_update_mint(true, Some(&[0u8; 32])).is_err());
865 assert!(
866 encode_initialize_mint(Some(&Address::new_from_array([0; 32])), true, None).is_err()
867 );
868 }
869
870 #[test]
871 fn a_negative_offset_is_its_twos_complement_byte() {
872 let back = ProofLocation::InstructionOffset(NonZeroI8::new(-1).unwrap());
873 assert_eq!(back.offset_byte(), 0xff);
874 let next = ProofLocation::InstructionOffset(NonZeroI8::new(2).unwrap());
875 assert_eq!(next.offset_byte(), 2);
876 }
877}