Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[token-2022] Update confidential transfer fee extension for solana 1.16 #4896

Merged

Conversation

samkim-crypto
Copy link
Contributor

@samkim-crypto samkim-crypto commented Aug 1, 2023

Problem

The confidential transfer fee extension is not updated for solana 1.16

Summary of changes

The most importants changes in the first, second, and last commits.

b96f57a: Updated the WithdrawWithheldTokensFromMint instruction for Solana 1.16 and for it to support context states. This is pretty straightforward except for the part about adding the withheld feeds directly into a destination available balance.

The original plan (#4817) was to divide the withheld amount into low and high parts. Transfer amounts and fees in the instruction are divided into low and high bits so that they are easier to decrypt. When the transfer amount is accumulated into a destination account, the transfer amounts are added into the lo and hi pending balance fields. However, for the case of fees, they are merged and added to the single withheld fees field. Similarly, when these withheld fees are harvest into the mint, it is added to a single withheld fee field. If the withheld fees grow large then it could be inefficient for a withdraw withheld authority to decrypt the ciphertext.

We can choose to also break up the withheld fees into lo and hi parts like the pending balance to make it easier for withdraw withheld authority to decrypt the withheld fees in an account for mint. However, I ended up deciding against it because

  • Having two ciphertexts for withheld fees in each account requires extra space
  • More importantly, the instruction WithdrawWithheldTokensFromMint requires a CiphertextCiphertextEqualityProof. If there are two ciphertexts for withheld fees, then we have to either attach two instances of this zkp or batch them, which would require a new instruction in the proof program.
  • In the worst case that the withdraw withheld authority cannot decrypt the withheld fees directly from the mint info, it can look up the previously list of transactions and decrypt.

Previously, when the withheld fees in the mint are withdrawn to an account, they were added into the "lo" part of the pending balance of the destination account. This is fine, but a little unnatural. If the fees are already merged, then it should really be added into the available balance. So in this PR, I made the change. Since we are making changes to the available balance, I also added the new decryptable available balance field in the instruction.

e64c9f0: Updated the WithdrawWithheldTokensFromAccount instruction for Solana 1.16 and for it to support context states as above.

0ed218e: Small PR to unfeaturize the client function for HarvestWithheldTokensToMint instruction.

dcc8574: Updated the tests for the WithdrawWithheldTokensFromMint instruction. I added the ConfidentialTokenAccountMeta helper struct in tests/confidential_transfer_fee.rs, which is a stripped down version of ConfidentialTokensAccountMeta in tests/confidential_transfer.rs. I can re-use the helper struct from tests/confidential_transfer.rs, but I decided to just have a separate helper struct in test/confidential_transfer_fee.rs that does not deal with fields like memo. The rest of the tests were straightforward.

c582a53: Updated the tests for the WithdrawWithheldTokensFromAccounts instruction.

a00c50e: Added tests that deal with context states for the WithdrawWithheldTokensFromMint and WithdrawWithheldTokensFromAccounts instructions.

3a6b3e0: Right now, the instruction WithdrawWithheldTokensFromMint could be susceptible to front-running. The zero-knowledge proof that is needed for the instruction depends on the currently withheld amount ciphertext in the mint. If the withheld amount is constantly harvested into the mint, the zkp can be nullified.

This PR added EnableHarvestToMint and DisableHarvestToMint, which acts like {Enable,Disable}ConfidentialTransfers and {Enable,Disable}NonConfidentialTransfers for harvest. If the harvest fails due to front-running, the harvest authority can disable harvest, withdraw withheld tokens, and then enable harvest.

@samkim-crypto samkim-crypto added the WIP Work in progress label Aug 1, 2023
@samkim-crypto samkim-crypto force-pushed the token22-withdraw-withheld branch 2 times, most recently from 21545f8 to 995e1a7 Compare August 2, 2023 08:28
Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really great overall! The core logic is good, just a few little things to sort out

Comment on lines 54 to 63
impl ConfidentialTransferFeeConfig {
/// Return the account information needed to construct a `WithdrawWithheldTokensFromMint`
/// instruction.
#[cfg(not(target_os = "solana"))]
pub fn withheld_tokens_info(&self) -> WithheldTokensInfo {
WithheldTokensInfo {
withheld_amount: self.withheld_amount,
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems a bit backwards -- how about having a function on WithheldTokensInfo that takes &ConfidentialTransferFeeConfig? That way you can keep the #[cfg(not(target_os = "solana"))] bits all in account_info.rs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that is actually a good idea. Since WithheldTokensInfo just has one field withheld_amount, I think we can just work with the existing constructor new(withheld_amount: EncryptedWithheldAmount) -> Self rather than taking in the entire confidential transfer fee config.

But the suggestion appiles for the account info types in the regular confidential transfer extension, so I will apply the suggestion there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 410 to 412
if !authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions that this works with multisigs, but this check means that it only works with single authorities. It's fine if we don't want to support multisigs, but we should be clear in the instruction interface

Comment on lines 439 to 441
if !authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

@@ -367,41 +468,28 @@ pub(crate) fn process_instruction(
)
}
ConfidentialTransferFeeInstruction::WithdrawWithheldTokensFromMint => {
msg!("ConfidentialTransferInstruction::WithdrawWithheldTokensFromMint");
#[cfg(all(feature = "zk-ops", feature = "proof-program"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes the featurizing on zk-ops, so I think it'll fail to build with zk-ops disabled. I think we might need one more release using the feature off unfortunately, so could you restore that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry I missed this!

}
ConfidentialTransferFeeInstruction::WithdrawWithheldTokensFromAccounts => {
msg!("ConfidentialTransferInstruction::WithdrawWithheldTokensFromAccounts");
#[cfg(all(feature = "zk-ops", feature = "proof-program"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, please restore the feature check

joncinque
joncinque previously approved these changes Aug 10, 2023
Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one last little nit, then this is good to go!

#[cfg(not(all(feature = "zk-ops", feature = "proof_program")))]
{
Err(ProgramError::InvalidInstructionData)
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll also need the Error variant if the feature is off

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry I missed it!

#[cfg(not(all(feature = "zk-ops", feature = "proof_program")))]
{
Err(ProgramError::InvalidInstructionData)
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll also need the error variant if the feature is off

@mergify mergify bot dismissed joncinque’s stale review August 11, 2023 01:56

Pull request has been modified.

@samkim-crypto samkim-crypto merged commit 08197b0 into solana-labs:master Aug 11, 2023
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Closed
Development

Successfully merging this pull request may close these issues.

2 participants