Skip to content

Conversation

@joncinque
Copy link
Contributor

@joncinque joncinque commented Oct 24, 2025

Problem

The program / client / cli crates are still on SDK v2.

Summary of changes

Mostly straightforward, but upgrade everything to SDK v3! Intra-repodependencies are restored for the most part, and breaking changes are dealt with as needed.

The most noteworthy change is the addition of a TryFrom<u32> implementation for TokenError, which allows us to print the error appropriately.

Otherwise, most of the changes are similar to other sdkv3 upgrades.

Fixes #711

#### Problem

The program / client / cli crates are still on SDK v2.

#### Summary of changes

Mostly straightforward, but upgrade everything to SDK v3! Intra-repo
dependencies are restored for the most part, and breaking changes are
dealt with as needed.

The most noteworthy change is the addition of a `TryFrom<u32>`
implementation for `TokenError`, which allows us to print the error
appropriately.
@joncinque joncinque requested a review from rustopian October 24, 2025 21:03
@joncinque joncinque marked this pull request as ready for review October 24, 2025 21:09
Copy link

@rustopian rustopian left a comment

Choose a reason for hiding this comment

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

Looks great on the whole, but the manual error matching looks like overkill, since we're already using num_traits/num_derive and can take advantage of it.

};

/// Errors that may be returned by the Token program.
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]

Choose a reason for hiding this comment

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

As part of the reduced try_from:

Suggested change
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
#[repr(u32)]
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]

Comment on lines 280 to 353
fn try_from(error: u32) -> Result<Self, Self::Error> {
match error {
0 => Ok(Self::NotRentExempt),
1 => Ok(Self::InsufficientFunds),
2 => Ok(Self::InvalidMint),
3 => Ok(Self::MintMismatch),
4 => Ok(Self::OwnerMismatch),
5 => Ok(Self::FixedSupply),
6 => Ok(Self::AlreadyInUse),
7 => Ok(Self::InvalidNumberOfProvidedSigners),
8 => Ok(Self::InvalidNumberOfRequiredSigners),
9 => Ok(Self::UninitializedState),
10 => Ok(Self::NativeNotSupported),
11 => Ok(Self::NonNativeHasBalance),
12 => Ok(Self::InvalidInstruction),
13 => Ok(Self::InvalidState),
14 => Ok(Self::Overflow),
15 => Ok(Self::AuthorityTypeNotSupported),
16 => Ok(Self::MintCannotFreeze),
17 => Ok(Self::AccountFrozen),
18 => Ok(Self::MintDecimalsMismatch),
19 => Ok(Self::NonNativeNotSupported),
20 => Ok(Self::ExtensionTypeMismatch),
21 => Ok(Self::ExtensionBaseMismatch),
22 => Ok(Self::ExtensionAlreadyInitialized),
23 => Ok(Self::ConfidentialTransferAccountHasBalance),
24 => Ok(Self::ConfidentialTransferAccountNotApproved),
25 => Ok(Self::ConfidentialTransferDepositsAndTransfersDisabled),
26 => Ok(Self::ConfidentialTransferElGamalPubkeyMismatch),
27 => Ok(Self::ConfidentialTransferBalanceMismatch),
28 => Ok(Self::MintHasSupply),
29 => Ok(Self::NoAuthorityExists),
30 => Ok(Self::TransferFeeExceedsMaximum),
31 => Ok(Self::MintRequiredForTransfer),
32 => Ok(Self::FeeMismatch),
33 => Ok(Self::FeeParametersMismatch),
34 => Ok(Self::ImmutableOwner),
35 => Ok(Self::AccountHasWithheldTransferFees),
36 => Ok(Self::NoMemo),
37 => Ok(Self::NonTransferable),
38 => Ok(Self::NonTransferableNeedsImmutableOwnership),
39 => Ok(Self::MaximumPendingBalanceCreditCounterExceeded),
40 => Ok(Self::MaximumDepositAmountExceeded),
41 => Ok(Self::CpiGuardSettingsLocked),
42 => Ok(Self::CpiGuardTransferBlocked),
43 => Ok(Self::CpiGuardBurnBlocked),
44 => Ok(Self::CpiGuardCloseAccountBlocked),
45 => Ok(Self::CpiGuardApproveBlocked),
46 => Ok(Self::CpiGuardSetAuthorityBlocked),
47 => Ok(Self::CpiGuardOwnerChangeBlocked),
48 => Ok(Self::ExtensionNotFound),
49 => Ok(Self::NonConfidentialTransfersDisabled),
50 => Ok(Self::ConfidentialTransferFeeAccountHasWithheldFee),
51 => Ok(Self::InvalidExtensionCombination),
52 => Ok(Self::InvalidLengthForAlloc),
53 => Ok(Self::AccountDecryption),
54 => Ok(Self::ProofGeneration),
55 => Ok(Self::InvalidProofInstructionOffset),
56 => Ok(Self::HarvestToMintDisabled),
57 => Ok(Self::SplitProofContextStateAccountsNotSupported),
58 => Ok(Self::NotEnoughProofContextStateAccounts),
59 => Ok(Self::MalformedCiphertext),
60 => Ok(Self::CiphertextArithmeticFailed),
61 => Ok(Self::PedersenCommitmentMismatch),
62 => Ok(Self::RangeProofLengthMismatch),
63 => Ok(Self::IllegalBitLength),
64 => Ok(Self::FeeCalculation),
65 => Ok(Self::IllegalMintBurnConversion),
66 => Ok(Self::InvalidScale),
67 => Ok(Self::MintPaused),
68 => Ok(Self::PendingBalanceNonZero),
_ => Err(ProgramError::InvalidArgument),
}
}

Choose a reason for hiding this comment

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

We can avoid the huge match section (and make it future-proof) by using #[repr(u32)] and taking advantage of num_derive::FromPrimitive, which is already derived and provides from_u32() -> Option

Suggested change
fn try_from(error: u32) -> Result<Self, Self::Error> {
match error {
0 => Ok(Self::NotRentExempt),
1 => Ok(Self::InsufficientFunds),
2 => Ok(Self::InvalidMint),
3 => Ok(Self::MintMismatch),
4 => Ok(Self::OwnerMismatch),
5 => Ok(Self::FixedSupply),
6 => Ok(Self::AlreadyInUse),
7 => Ok(Self::InvalidNumberOfProvidedSigners),
8 => Ok(Self::InvalidNumberOfRequiredSigners),
9 => Ok(Self::UninitializedState),
10 => Ok(Self::NativeNotSupported),
11 => Ok(Self::NonNativeHasBalance),
12 => Ok(Self::InvalidInstruction),
13 => Ok(Self::InvalidState),
14 => Ok(Self::Overflow),
15 => Ok(Self::AuthorityTypeNotSupported),
16 => Ok(Self::MintCannotFreeze),
17 => Ok(Self::AccountFrozen),
18 => Ok(Self::MintDecimalsMismatch),
19 => Ok(Self::NonNativeNotSupported),
20 => Ok(Self::ExtensionTypeMismatch),
21 => Ok(Self::ExtensionBaseMismatch),
22 => Ok(Self::ExtensionAlreadyInitialized),
23 => Ok(Self::ConfidentialTransferAccountHasBalance),
24 => Ok(Self::ConfidentialTransferAccountNotApproved),
25 => Ok(Self::ConfidentialTransferDepositsAndTransfersDisabled),
26 => Ok(Self::ConfidentialTransferElGamalPubkeyMismatch),
27 => Ok(Self::ConfidentialTransferBalanceMismatch),
28 => Ok(Self::MintHasSupply),
29 => Ok(Self::NoAuthorityExists),
30 => Ok(Self::TransferFeeExceedsMaximum),
31 => Ok(Self::MintRequiredForTransfer),
32 => Ok(Self::FeeMismatch),
33 => Ok(Self::FeeParametersMismatch),
34 => Ok(Self::ImmutableOwner),
35 => Ok(Self::AccountHasWithheldTransferFees),
36 => Ok(Self::NoMemo),
37 => Ok(Self::NonTransferable),
38 => Ok(Self::NonTransferableNeedsImmutableOwnership),
39 => Ok(Self::MaximumPendingBalanceCreditCounterExceeded),
40 => Ok(Self::MaximumDepositAmountExceeded),
41 => Ok(Self::CpiGuardSettingsLocked),
42 => Ok(Self::CpiGuardTransferBlocked),
43 => Ok(Self::CpiGuardBurnBlocked),
44 => Ok(Self::CpiGuardCloseAccountBlocked),
45 => Ok(Self::CpiGuardApproveBlocked),
46 => Ok(Self::CpiGuardSetAuthorityBlocked),
47 => Ok(Self::CpiGuardOwnerChangeBlocked),
48 => Ok(Self::ExtensionNotFound),
49 => Ok(Self::NonConfidentialTransfersDisabled),
50 => Ok(Self::ConfidentialTransferFeeAccountHasWithheldFee),
51 => Ok(Self::InvalidExtensionCombination),
52 => Ok(Self::InvalidLengthForAlloc),
53 => Ok(Self::AccountDecryption),
54 => Ok(Self::ProofGeneration),
55 => Ok(Self::InvalidProofInstructionOffset),
56 => Ok(Self::HarvestToMintDisabled),
57 => Ok(Self::SplitProofContextStateAccountsNotSupported),
58 => Ok(Self::NotEnoughProofContextStateAccounts),
59 => Ok(Self::MalformedCiphertext),
60 => Ok(Self::CiphertextArithmeticFailed),
61 => Ok(Self::PedersenCommitmentMismatch),
62 => Ok(Self::RangeProofLengthMismatch),
63 => Ok(Self::IllegalBitLength),
64 => Ok(Self::FeeCalculation),
65 => Ok(Self::IllegalMintBurnConversion),
66 => Ok(Self::InvalidScale),
67 => Ok(Self::MintPaused),
68 => Ok(Self::PendingBalanceNonZero),
_ => Err(ProgramError::InvalidArgument),
}
}
#[inline]
fn try_from(code: u32) -> Result<Self, Self::Error> {
num_traits::FromPrimitive::from_u32(code).ok_or(ProgramError::InvalidArgument)
}

@joncinque
Copy link
Contributor Author

Beautiful, thanks for the suggestion! I completely forgot that we were using num-traits in this crate 😅

@rustopian rustopian self-requested a review October 28, 2025 11:49
Copy link

@rustopian rustopian left a comment

Choose a reason for hiding this comment

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

🙌

@joncinque joncinque merged commit 9d88568 into solana-program:main Oct 28, 2025
33 checks passed
@joncinque joncinque deleted the sdkv3 branch October 28, 2025 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update token-2022 program to use 3.0 crates and 2.0 interface crates

2 participants