token-2022: Add confidential transfer with fee instruction (#2988)
* token-2022: add separate transfer with fee instruction * token-2022: add `TransferWithFee` client code * apply twoxtx patch * token-2022: add brief tests for transfer with fee * Revert "apply twoxtx patch" This reverts commit ce09d1f5d2fb496cd4ee9991be234726786e39f2. * token-2022: cargo fmt * token-2022: uncommenting the rest of the tests * token-2022: cargo fmt * token-2022: temporarily reverting to 5f89521 * token-2022: minor * token-2022: clippy * token-2022: apply twoxtx patch * token-2022: fix transfer with fee test * Revert "token-2022: apply twoxtx patch" This reverts commit 577e63c2f38ce0a17fa4aede3d0acfd852b1d3ab. * token-2022: simplify fee parameter for zkp on client * token-2022: fix build
This commit is contained in:
parent
b5e301b210
commit
049a89f351
|
@ -166,9 +166,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.52"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
|
||||
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.36",
|
||||
"quote 1.0.14",
|
||||
|
@ -245,7 +245,7 @@ dependencies = [
|
|||
"getrandom 0.2.3",
|
||||
"instant",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
@ -1296,9 +1296,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
||||
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crc32fast",
|
||||
|
@ -1621,9 +1621,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "hidapi"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "253bfb01a7a31d71212dc3e920540546fa4a4fe08e2d87fc3299c42bae7ee2f9"
|
||||
checksum = "38b1717343691998deb81766bfcd1dce6df0d5d6c37070b5a3de2bb6d39f7822"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -1937,9 +1937,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.55"
|
||||
version = "0.3.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
|
||||
checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -2087,9 +2087,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.121"
|
||||
version = "0.2.124"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
||||
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -2321,12 +2321,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2615,7 +2614,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"percent-encoding 2.1.0",
|
||||
"pin-project",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
@ -2970,7 +2969,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"num-traits",
|
||||
"quick-error 2.0.1",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
|
@ -3129,7 +3128,7 @@ checksum = "359c5eb33845f3ee05c229e65f87cdbc503eea394964b8f1330833d460b4ff3e"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"fxhash",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
|
@ -3184,20 +3183,19 @@ dependencies = [
|
|||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc 0.2.0",
|
||||
"rand_hc",
|
||||
"rand_pcg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.3",
|
||||
"rand_hc 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3247,15 +3245,6 @@ dependencies = [
|
|||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.2.1"
|
||||
|
@ -3285,9 +3274,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.1"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
|
||||
checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
|
@ -3297,14 +3286,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.9.1"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
|
||||
checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
|
@ -3949,7 +3937,7 @@ dependencies = [
|
|||
"futures 0.3.21",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"sha-1 0.9.8",
|
||||
]
|
||||
|
||||
|
@ -5942,7 +5930,7 @@ dependencies = [
|
|||
"humantime",
|
||||
"opentelemetry",
|
||||
"pin-project",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"tarpc-plugins",
|
||||
|
@ -6341,7 +6329,7 @@ dependencies = [
|
|||
"indexmap",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util 0.7.1",
|
||||
|
@ -6471,7 +6459,7 @@ dependencies = [
|
|||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"rand 0.8.5",
|
||||
"rustls",
|
||||
"sha-1 0.10.0",
|
||||
"thiserror",
|
||||
|
@ -6595,9 +6583,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
|||
|
||||
[[package]]
|
||||
name = "uriparse"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221"
|
||||
checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
|
@ -6716,9 +6704,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -6726,9 +6714,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.78"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
|
||||
checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
|
@ -6753,9 +6741,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.78"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
|
||||
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
|
||||
dependencies = [
|
||||
"quote 1.0.14",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -6763,9 +6751,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.78"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
|
||||
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.36",
|
||||
"quote 1.0.14",
|
||||
|
@ -6776,9 +6764,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.78"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::client::{ProgramClient, ProgramClientError, SendTransaction};
|
|||
use solana_program_test::tokio::time;
|
||||
use solana_sdk::{
|
||||
account::Account as BaseAccount,
|
||||
epoch_info::EpochInfo,
|
||||
hash::Hash,
|
||||
instruction::Instruction,
|
||||
program_error::ProgramError,
|
||||
|
@ -22,6 +23,7 @@ use spl_token_2022::{
|
|||
solana_zk_token_sdk::{
|
||||
encryption::{auth_encryption::*, elgamal::*},
|
||||
errors::ProofError,
|
||||
instruction::transfer_with_fee::FeeParameters,
|
||||
},
|
||||
state::{Account, AccountState, Mint},
|
||||
};
|
||||
|
@ -1148,6 +1150,80 @@ where
|
|||
.await
|
||||
}
|
||||
|
||||
/// Transfer tokens confidentially with fee
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn confidential_transfer_transfer_with_fee<S2: Signer>(
|
||||
&self,
|
||||
source_token_account: &Pubkey,
|
||||
destination_token_account: &Pubkey,
|
||||
source_token_authority: &S2,
|
||||
amount: u64,
|
||||
source_available_balance: u64,
|
||||
source_elgamal_keypair: &ElGamalKeypair,
|
||||
new_source_decryptable_available_balance: AeCiphertext,
|
||||
epoch_info: &EpochInfo,
|
||||
) -> TokenResult<T::Output> {
|
||||
let source_state = self.get_account_info(source_token_account).await.unwrap();
|
||||
let source_extension =
|
||||
source_state.get_extension::<confidential_transfer::ConfidentialTransferAccount>()?;
|
||||
|
||||
let destination_state = self
|
||||
.get_account_info(destination_token_account)
|
||||
.await
|
||||
.unwrap();
|
||||
let destination_extension = destination_state
|
||||
.get_extension::<confidential_transfer::ConfidentialTransferAccount>(
|
||||
)?;
|
||||
|
||||
let mint_state = self.get_mint_info().await.unwrap();
|
||||
let transfer_fee_config = mint_state
|
||||
.get_extension::<transfer_fee::TransferFeeConfig>()
|
||||
.unwrap();
|
||||
|
||||
let fee_parameters = transfer_fee_config.get_epoch_fee(epoch_info.epoch);
|
||||
|
||||
let ct_mint = mint_state
|
||||
.get_extension::<confidential_transfer::ConfidentialTransferMint>()
|
||||
.unwrap();
|
||||
|
||||
let proof_data = confidential_transfer::instruction::TransferWithFeeData::new(
|
||||
amount,
|
||||
(
|
||||
source_available_balance,
|
||||
&source_extension.available_balance.try_into().unwrap(),
|
||||
),
|
||||
source_elgamal_keypair,
|
||||
(
|
||||
&destination_extension.encryption_pubkey.try_into().unwrap(),
|
||||
&ct_mint.auditor_pubkey.try_into().unwrap(),
|
||||
),
|
||||
FeeParameters {
|
||||
fee_rate_basis_points: u16::from(fee_parameters.transfer_fee_basis_points),
|
||||
maximum_fee: u64::from(fee_parameters.maximum_fee),
|
||||
},
|
||||
&ct_mint
|
||||
.withdraw_withheld_authority_pubkey
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.map_err(TokenError::Proof)?;
|
||||
|
||||
self.process_ixs(
|
||||
&confidential_transfer::instruction::transfer_with_fee(
|
||||
&self.program_id,
|
||||
source_token_account,
|
||||
destination_token_account,
|
||||
&self.pubkey,
|
||||
new_source_decryptable_available_balance,
|
||||
&source_token_authority.pubkey(),
|
||||
&[],
|
||||
&proof_data,
|
||||
)?,
|
||||
&[source_token_authority],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Applies the confidential transfer pending balance to the available balance
|
||||
pub async fn confidential_transfer_apply_pending_balance<S2: Signer>(
|
||||
&self,
|
||||
|
|
|
@ -6,10 +6,11 @@ use {
|
|||
program_test::{TestContext, TokenContext},
|
||||
solana_program_test::tokio,
|
||||
solana_sdk::{
|
||||
instruction::InstructionError, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair,
|
||||
transaction::TransactionError, transport::TransportError,
|
||||
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,
|
||||
|
@ -28,6 +29,20 @@ use {
|
|||
std::convert::TryInto,
|
||||
};
|
||||
|
||||
const TEST_MAXIMUM_FEE: u64 = 100;
|
||||
const TEST_FEE_BASIS_POINTS: u16 = 250;
|
||||
|
||||
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,
|
||||
|
@ -703,3 +718,194 @@ async fn ct_transfer() {
|
|||
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();
|
||||
|
||||
// Self-transfer of N tokens
|
||||
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(100_u64),
|
||||
&epoch_info,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Fee is 2.5%, so what is left in 97 in Alice account
|
||||
token
|
||||
.confidential_transfer_apply_pending_balance(
|
||||
&alice_meta.token_account,
|
||||
&alice,
|
||||
2,
|
||||
alice_meta.ae_key.encrypt(97_u64),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
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(97),
|
||||
);
|
||||
|
||||
token
|
||||
.confidential_transfer_transfer_with_fee(
|
||||
&alice_meta.token_account,
|
||||
&bob_meta.token_account,
|
||||
&alice,
|
||||
97, // amount
|
||||
97, // available balance
|
||||
&alice_meta.elgamal_keypair,
|
||||
alice_meta.ae_key.encrypt(0_u64),
|
||||
&epoch_info,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
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
|
||||
let error = token
|
||||
.confidential_transfer_empty_account(
|
||||
&alice_meta.token_account,
|
||||
&alice,
|
||||
&alice_meta.elgamal_keypair,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error,
|
||||
TokenClientError::Client(Box::new(TransportError::TransactionError(
|
||||
TransactionError::InstructionError(
|
||||
1,
|
||||
InstructionError::Custom(TokenError::ConfidentialTransferAccountHasBalance as u32)
|
||||
)
|
||||
)))
|
||||
);
|
||||
|
||||
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();
|
||||
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();
|
||||
assert_eq!(
|
||||
bob_meta
|
||||
.ae_key
|
||||
.decrypt(&extension.decryptable_available_balance.try_into().unwrap()),
|
||||
Some(94),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -201,6 +201,28 @@ pub enum ConfidentialTransferInstruction {
|
|||
///
|
||||
Transfer,
|
||||
|
||||
/// Transfer tokens confidentially with fee.
|
||||
///
|
||||
/// * Single owner/delegate
|
||||
/// 1. `[writable]` The source SPL Token account.
|
||||
/// 2. `[writable]` The destination SPL Token account.
|
||||
/// 3. `[]` The token mint.
|
||||
/// 4. `[]` Instructions sysvar.
|
||||
/// 5. `[signer]` The single source account owner.
|
||||
///
|
||||
/// * Multisignature owner/delegate
|
||||
/// 1. `[writable]` The source SPL Token account.
|
||||
/// 2. `[writable]` The destination SPL Token account.
|
||||
/// 3. `[]` The token mint.
|
||||
/// 4. `[]` Instructions sysvar.
|
||||
/// 5. `[]` The multisig source account owner.
|
||||
/// 6.. `[signer]` Required M signer accounts for the SPL Token Multisig account.
|
||||
///
|
||||
/// Data expected by this instruction:
|
||||
/// `TransferWithFeeInstructionData`
|
||||
///
|
||||
TransferWithFee,
|
||||
|
||||
/// Applies the pending balance to the available balance, based on the history of `Deposit`
|
||||
/// and/or `Transfer` instructions.
|
||||
///
|
||||
|
@ -404,6 +426,17 @@ pub struct TransferInstructionData {
|
|||
pub proof_instruction_offset: i8,
|
||||
}
|
||||
|
||||
/// Data expected by `ConfidentialTransferInstruction::TransferWithFee`
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct TransferWithFeeInstructionData {
|
||||
/// The new source decryptable balance if the transfer succeeds
|
||||
pub new_source_decryptable_available_balance: DecryptableBalance,
|
||||
/// Relative location of the `ProofInstruction::VerifyTransfer` instruction to the
|
||||
/// `Transfer` instruction in the transaction
|
||||
pub proof_instruction_offset: i8,
|
||||
}
|
||||
|
||||
/// Data expected by `ConfidentialTransferInstruction::ApplyPendingBalance`
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
|
@ -763,6 +796,73 @@ pub fn transfer(
|
|||
])
|
||||
}
|
||||
|
||||
/// Create a inner `TransferWithFee` instruction
|
||||
///
|
||||
/// This instruction is suitable for use with a cross-program `invoke`
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn inner_transfer_with_fee(
|
||||
token_program_id: &Pubkey,
|
||||
source_token_account: &Pubkey,
|
||||
destination_token_account: &Pubkey,
|
||||
mint: &Pubkey,
|
||||
new_source_decryptable_available_balance: DecryptableBalance,
|
||||
authority: &Pubkey,
|
||||
multisig_signers: &[&Pubkey],
|
||||
proof_instruction_offset: i8,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
check_program_account(token_program_id)?;
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new(*source_token_account, false),
|
||||
AccountMeta::new(*destination_token_account, false),
|
||||
AccountMeta::new_readonly(*mint, false),
|
||||
AccountMeta::new_readonly(sysvar::instructions::id(), false),
|
||||
AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
|
||||
];
|
||||
|
||||
for multisig_signer in multisig_signers.iter() {
|
||||
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
|
||||
}
|
||||
|
||||
Ok(encode_instruction(
|
||||
token_program_id,
|
||||
accounts,
|
||||
TokenInstruction::ConfidentialTransferExtension,
|
||||
ConfidentialTransferInstruction::TransferWithFee,
|
||||
&TransferWithFeeInstructionData {
|
||||
new_source_decryptable_available_balance,
|
||||
proof_instruction_offset,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a `Transfer` instruction
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
pub fn transfer_with_fee(
|
||||
token_program_id: &Pubkey,
|
||||
source_token_account: &Pubkey,
|
||||
destination_token_account: &Pubkey,
|
||||
mint: &Pubkey,
|
||||
new_source_decryptable_available_balance: AeCiphertext,
|
||||
authority: &Pubkey,
|
||||
multisig_signers: &[&Pubkey],
|
||||
proof_data: &TransferWithFeeData,
|
||||
) -> Result<Vec<Instruction>, ProgramError> {
|
||||
Ok(vec![
|
||||
verify_transfer_with_fee(proof_data),
|
||||
inner_transfer_with_fee(
|
||||
token_program_id,
|
||||
source_token_account,
|
||||
destination_token_account,
|
||||
mint,
|
||||
new_source_decryptable_available_balance.into(),
|
||||
authority,
|
||||
multisig_signers,
|
||||
-1,
|
||||
)?, // calls check_program_account
|
||||
])
|
||||
}
|
||||
|
||||
/// Create a inner `ApplyPendingBalance` instruction
|
||||
///
|
||||
/// This instruction is suitable for use with a cross-program `invoke`
|
||||
|
|
|
@ -33,6 +33,10 @@ fn decode_proof_instruction<T: Pod>(
|
|||
expected: ProofInstruction,
|
||||
instruction: &Instruction,
|
||||
) -> Result<&T, ProgramError> {
|
||||
if ProofInstruction::decode_type(&instruction.data) != Some(expected) {
|
||||
msg!("decode type failed ----------------------------------");
|
||||
}
|
||||
|
||||
if instruction.program_id != zk_token_proof_program::id()
|
||||
|| ProofInstruction::decode_type(&instruction.data) != Some(expected)
|
||||
{
|
||||
|
@ -241,11 +245,6 @@ fn process_empty_account(
|
|||
}
|
||||
|
||||
confidential_transfer_account.available_balance = EncryptedBalance::zeroed();
|
||||
|
||||
if confidential_transfer_account.withheld_amount != EncryptedWithheldAmount::zeroed() {
|
||||
msg!("Withheld amount is not zero");
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
confidential_transfer_account.closable()?;
|
||||
|
||||
Ok(())
|
||||
|
@ -482,7 +481,7 @@ fn process_transfer(
|
|||
if let Ok(transfer_fee_config) = mint.get_extension::<TransferFeeConfig>() {
|
||||
// mint is extended for fees
|
||||
let proof_data = decode_proof_instruction::<TransferWithFeeData>(
|
||||
ProofInstruction::VerifyTransfer,
|
||||
ProofInstruction::VerifyTransferWithFee,
|
||||
&previous_instruction,
|
||||
)?;
|
||||
|
||||
|
@ -578,6 +577,7 @@ fn process_transfer(
|
|||
proof_data.ciphertext_hi.commitment,
|
||||
proof_data.ciphertext_hi.source_handle,
|
||||
));
|
||||
|
||||
process_source_for_transfer(
|
||||
program_id,
|
||||
token_account_info,
|
||||
|
@ -1162,6 +1162,16 @@ pub(crate) fn process_instruction(
|
|||
#[cfg(not(feature = "zk-ops"))]
|
||||
Err(ProgramError::InvalidInstructionData)
|
||||
}
|
||||
ConfidentialTransferInstruction::TransferWithFee => {
|
||||
msg!("ConfidentialTransferInstruction::TransferWithFee");
|
||||
let data = decode_instruction_data::<TransferWithFeeInstructionData>(input)?;
|
||||
process_transfer(
|
||||
program_id,
|
||||
accounts,
|
||||
data.new_source_decryptable_available_balance,
|
||||
data.proof_instruction_offset as i64,
|
||||
)
|
||||
}
|
||||
ConfidentialTransferInstruction::ApplyPendingBalance => {
|
||||
msg!("ConfidentialTransferInstruction::ApplyPendingBalance");
|
||||
#[cfg(feature = "zk-ops")]
|
||||
|
|
Loading…
Reference in New Issue