feat: confidential rewards for continuous pools (Token-2022 CT)#17
feat: confidential rewards for continuous pools (Token-2022 CT)#17
Conversation
Adds opt-in confidential reward distribution to continuous reward pools using Token-2022 ConfidentialTransfer. Pool creators set `confidential_rewards` flag; claim/opt-out perform CT transfers with client-supplied ZK proofs; distribute does a CT deposit after the normal TransferChecked CPI. Includes ZK-ops-enabled Token-2022 binary fixture for integration tests (619 KB, Apache-2.0, from Surfpool zk-edge cluster) since LiteSVM's bundled binary lacks `--features zk-ops`. - RewardPool gains `confidential_rewards: u8` (occupies existing padding byte, DATA_LEN unchanged) - New `confidential_transfer.rs` CPI builder - `ConfidentialVaultState` + `configure_account`/`apply_pending_balance` client helpers - 5 new CT integration tests + ZK ElGamal probe test (301 tests total pass)
- Add `#[cfg(feature = "confidential")]` to `builder_extensions.rs` so it is consistent with `confidential_helpers.rs` - `cargo fmt --all` cleanup
- distribute: add expected_pending_balance_credit_counter to instruction data (hardcoded 0 would fail in production when deposit increments counter) - revoke_user: add CT transfer path (TransferChecked fails on CT vaults) - close_pool: guard against closing with unclaimed CT rewards - claim/opt_out: validate proof context accounts are owned by ZK ElGamal program - opt_in: check CT ATA size >= 460 bytes before accepting - confidential_transfer.rs: replace MaybeUninit with zero-initialized arrays - confidential_helpers: clarify effective_amount vs raw amount in docstring
38fbf87 to
d1b9f5e
Compare
…ousReward The program's data.rs already required 16 bytes (amount + counter) but definition.rs and the generated client only serialised 8 bytes (amount), causing InvalidInstructionData on every distribute call. - Add expected_pending_balance_credit_counter: u64 to DistributeContinuousReward in definition.rs; regenerate IDL and client - Pass counter=0 in all non-confidential test builders - Pass counter=1 in confidential test builders (one ConfidentialDeposit fires in the same tx, incrementing the vault counter from 0 to 1) - Raise fake Token-2022 ATA size in lifecycle test from 300 to 460 bytes to satisfy the updated CT opt-in threshold (>= 460)
- Refactor CT branching into shared utils: transfer_reward_tokens and maybe_confidential_deposit consolidate the if/else blocks that were duplicated across Claim, OptOut, RevokeContinuousUser, and Distribute - Add ConfidentialWithdraw and ConfidentialEmptyAccount CPIs to enable CloseContinuousPool on CT pools with unclaimed rewards: Withdraw converts CT balance to plaintext, EmptyAccount zeros the ciphertext bytes (required for CloseAccount), TransferChecked sweeps - close_pool accepts optional trailing data (new_decryptable) and three ZK proof context accounts (equality, range, zero_ciphertext) when pool.confidential_rewards != 0 and total_distributed != total_claimed - Add CloseContinuousPoolBuilder::confidential_close and ConfidentialVaultState::prepare_close to the client helpers - 336 integration tests pass, 377 unit tests pass
| pub reward_token_program: &'a AccountView, | ||
| pub event_authority: &'a AccountView, | ||
| pub program: &'a AccountView, | ||
| /// Required when `pool.confidential_rewards != 0`. |
There was a problem hiding this comment.
Util to have those extra 3 accounts or macro, since it's used across all the different "accounts.rs" for each instruction that supports it
| tracked_token_program, | ||
| )?; | ||
|
|
||
| let (equality_proof_context, ciphertext_validity_proof_context, range_proof_context) = if accounts.len() >= 15 { |
There was a problem hiding this comment.
shared utils since its used by all "accounts.rs"
| let amount = u64::from_le_bytes(data[0..8].try_into().map_err(|_| ProgramError::InvalidInstructionData)?); | ||
|
|
||
| Ok(Self { amount }) | ||
| let confidential_transfer_bytes = if data.len() >= Self::LEN + CONFIDENTIAL_TRANSFER_DATA_LEN { |
There was a problem hiding this comment.
shared util that takes expected len, since used across instructions supporting conf balance
| // For confidential pools with unclaimed rewards: convert the vault's CT available | ||
| // balance back to plaintext so the existing TransferChecked sweep can handle it. | ||
| if pool.confidential_rewards != 0 && pool.total_distributed != pool.total_claimed { | ||
| let withdrawal_amount = |
There was a problem hiding this comment.
if pool confidential, should have all the conf balance utils in its own util file for confidential logic, since it might be reused across the code
| /// where `counter` is the vault's current `pending_balance_credit_counter` | ||
| /// read off-chain before building the transaction (one `ConfidentialDeposit` | ||
| /// increments it in the same tx). | ||
| pub expected_pending_balance_credit_counter: u64, |
There was a problem hiding this comment.
Wondering should we have instructions for conf balances specifically? Feels like we're adding a lot of option<> to code that most of the time might not need it
| // extension (base 165 bytes + CT extension ~295 bytes = ~460 bytes minimum). Accounts | ||
| // smaller than this cannot have had ConfigureAccount called on them. | ||
| verify_owned_by(reward_ata, &pinocchio_token_2022::ID)?; | ||
| if reward_ata.data_len() < 460 { |
There was a problem hiding this comment.
should be util in conf util
| @@ -0,0 +1,86 @@ | |||
| /// Probe: does LiteSVM's ZK ElGamal proof program work end-to-end? | |||
| /// | |||
There was a problem hiding this comment.
this should be removed completely (the file)
|
Closing, will most likely become it's own program at some point instead. |
Summary
Adds opt-in confidential reward distribution to continuous reward pools using the Token-2022
ConfidentialTransferextension.confidential_rewards: u8flag onCreateContinuousPool— no change toRewardPool::DATA_LEN(occupies an existing padding byte)ClaimContinuousandContinuousOptOutperform CT transfers using client-supplied ZK proof context accounts (equality + validity + range proofs)DistributeContinuousRewardissues aConfidentialDepositCPI afterTransferCheckedfor confidential poolsContinuousOptInvalidates that the user's token account has CT configured when the pool is confidentialconfidential_transfer.rsCPI builder in the programconfidential_helpers.rsin the Rust client:ConfidentialVaultState,configure_account(),apply_pending_balance()ZK-ops Token-2022 binary
LiteSVM ships Token-2022 compiled without
--features zk-ops, causingConfidentialDeposit/ConfidentialTransferto returnInvalidInstructionData. Tests load a ZK-ops-enabled build sourced from Surfpool's public zk-edge cluster (Apache-2.0, 619 KB) committed astests/integration-tests/src/programs/spl_token_2022_zk.so.Tests
5 new CT integration tests + ZK ElGamal probe test.
Files changed
claim,create_pool,distribute_reward,opt_in,opt_out(accounts + data + processor)reward_pool.rs—confidential_rewards: u8fieldconfidential_transfer.rs(new),continuous_utils.rs,mod.rslib.rs,confidential_helpers.rs(new)setup.rs,spl_token_2022_zk.sorewards_program.json,CU_BENCHMARKS.md