Skip to content

Commit b7cfafc

Browse files
authored
Introduce canonical pointer PDA (#374)
* Introduce canonical pointer PDA * add tests * Spellcheck * lint fixes * review updates
1 parent 5ed5ad0 commit b7cfafc

File tree

9 files changed

+632
-15
lines changed

9 files changed

+632
-15
lines changed

program/src/instruction.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,33 @@ pub enum TokenWrapInstruction {
170170
/// 8. `[]` (Optional) Owner program. Required when metadata account is
171171
/// owned by a third-party program.
172172
SyncMetadataToSplToken,
173+
174+
/// Creates or updates the canonical program pointer for a mint.
175+
///
176+
/// The mint authority of an unwrapped mint may desire to deploy a forked
177+
/// version of the Token Wrap program themselves. It's likely the case they
178+
/// prefer a certain set of extensions or a particular config for the
179+
/// wrapped token-2022s. They may even freeze the unwrapped mint's
180+
/// escrow account in the original deployment to force the use of the fork.
181+
/// A `CanonicalPointer` PDA allows a mint authority to signal on-chain
182+
/// another Token Wrap deployment is the "canonical" one for the mint.
183+
///
184+
/// If calling for the first time, the client is responsible for pre-funding
185+
/// the rent for the PDA that will be initialized.
186+
///
187+
/// If no mint authority exists on the unwrapped mint, this instruction will
188+
/// fail.
189+
///
190+
/// Accounts expected:
191+
/// 0. `[s]` Unwrapped mint authority
192+
/// 1. `[w]` `CanonicalPointer` PDA account to create or update, address
193+
/// must be: `get_canonical_pointer_address(unwrapped_mint_address)`
194+
/// 2. `[]` Unwrapped mint
195+
/// 3. `[]` System program
196+
SetCanonicalPointer {
197+
/// The program ID to set as canonical
198+
program_id: Pubkey,
199+
},
173200
}
174201

175202
impl TokenWrapInstruction {
@@ -200,6 +227,10 @@ impl TokenWrapInstruction {
200227
TokenWrapInstruction::SyncMetadataToSplToken => {
201228
buf.push(5);
202229
}
230+
TokenWrapInstruction::SetCanonicalPointer { program_id } => {
231+
buf.push(6);
232+
buf.extend_from_slice(program_id.as_ref());
233+
}
203234
}
204235
buf
205236
}
@@ -227,6 +258,10 @@ impl TokenWrapInstruction {
227258
Some((&3, [])) => Ok(TokenWrapInstruction::CloseStuckEscrow),
228259
Some((&4, [])) => Ok(TokenWrapInstruction::SyncMetadataToToken2022),
229260
Some((&5, [])) => Ok(TokenWrapInstruction::SyncMetadataToSplToken),
261+
Some((&6, rest)) if rest.len() == 32 => {
262+
let program_id = Pubkey::new_from_array(rest.try_into().unwrap());
263+
Ok(TokenWrapInstruction::SetCanonicalPointer { program_id })
264+
}
230265
_ => Err(ProgramError::InvalidInstructionData),
231266
}
232267
}
@@ -408,3 +443,24 @@ pub fn sync_metadata_to_spl_token(
408443
let data = TokenWrapInstruction::SyncMetadataToSplToken.pack();
409444
Instruction::new_with_bytes(*program_id, &data, accounts)
410445
}
446+
447+
/// Creates `SetCanonicalPointer` instruction.
448+
pub fn set_canonical_pointer(
449+
program_id: &Pubkey,
450+
mint_authority: &Pubkey,
451+
pointer_address: &Pubkey,
452+
unwrapped_mint: &Pubkey,
453+
canonical_program_id: &Pubkey,
454+
) -> Instruction {
455+
let accounts = vec![
456+
AccountMeta::new_readonly(*mint_authority, true),
457+
AccountMeta::new(*pointer_address, false),
458+
AccountMeta::new_readonly(*unwrapped_mint, false),
459+
AccountMeta::new_readonly(solana_system_interface::program::id(), false),
460+
];
461+
let data = TokenWrapInstruction::SetCanonicalPointer {
462+
program_id: *canonical_program_id,
463+
}
464+
.pack();
465+
Instruction::new_with_bytes(*program_id, &data, accounts)
466+
}

program/src/lib.rs

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,18 @@ const WRAPPED_MINT_SEED: &[u8] = br"mint";
2323
pub(crate) fn get_wrapped_mint_address_with_seed(
2424
unwrapped_mint: &Pubkey,
2525
wrapped_token_program_id: &Pubkey,
26+
) -> (Pubkey, u8) {
27+
get_wrapped_mint_address_with_seed_for_program(unwrapped_mint, wrapped_token_program_id, &id())
28+
}
29+
30+
pub(crate) fn get_wrapped_mint_address_with_seed_for_program(
31+
unwrapped_mint: &Pubkey,
32+
wrapped_token_program_id: &Pubkey,
33+
program_id: &Pubkey,
2634
) -> (Pubkey, u8) {
2735
Pubkey::find_program_address(
2836
&get_wrapped_mint_seeds(unwrapped_mint, wrapped_token_program_id),
29-
&id(),
37+
program_id,
3038
)
3139
}
3240

@@ -59,7 +67,22 @@ pub fn get_wrapped_mint_address(
5967
unwrapped_mint: &Pubkey,
6068
wrapped_token_program_id: &Pubkey,
6169
) -> Pubkey {
62-
get_wrapped_mint_address_with_seed(unwrapped_mint, wrapped_token_program_id).0
70+
get_wrapped_mint_address_for_program(unwrapped_mint, wrapped_token_program_id, &id())
71+
}
72+
73+
/// Derive the SPL Token wrapped mint address associated with an unwrapped mint
74+
/// for a specific Token Wrap program deployment.
75+
pub fn get_wrapped_mint_address_for_program(
76+
unwrapped_mint: &Pubkey,
77+
wrapped_token_program_id: &Pubkey,
78+
program_id: &Pubkey,
79+
) -> Pubkey {
80+
get_wrapped_mint_address_with_seed_for_program(
81+
unwrapped_mint,
82+
wrapped_token_program_id,
83+
program_id,
84+
)
85+
.0
6386
}
6487

6588
const WRAPPED_MINT_AUTHORITY_SEED: &[u8] = br"authority";
@@ -80,12 +103,28 @@ pub(crate) fn get_wrapped_mint_authority_signer_seeds<'a>(
80103
}
81104

82105
pub(crate) fn get_wrapped_mint_authority_with_seed(wrapped_mint: &Pubkey) -> (Pubkey, u8) {
83-
Pubkey::find_program_address(&get_wrapped_mint_authority_seeds(wrapped_mint), &id())
106+
get_wrapped_mint_authority_with_seed_for_program(wrapped_mint, &id())
107+
}
108+
109+
pub(crate) fn get_wrapped_mint_authority_with_seed_for_program(
110+
wrapped_mint: &Pubkey,
111+
program_id: &Pubkey,
112+
) -> (Pubkey, u8) {
113+
Pubkey::find_program_address(&get_wrapped_mint_authority_seeds(wrapped_mint), program_id)
84114
}
85115

86116
/// Derive the SPL Token wrapped mint authority address
87117
pub fn get_wrapped_mint_authority(wrapped_mint: &Pubkey) -> Pubkey {
88-
get_wrapped_mint_authority_with_seed(wrapped_mint).0
118+
get_wrapped_mint_authority_for_program(wrapped_mint, &id())
119+
}
120+
121+
/// Derive the SPL Token wrapped mint authority address for a specific Token
122+
/// Wrap program deployment
123+
pub fn get_wrapped_mint_authority_for_program(
124+
wrapped_mint: &Pubkey,
125+
program_id: &Pubkey,
126+
) -> Pubkey {
127+
get_wrapped_mint_authority_with_seed_for_program(wrapped_mint, program_id).0
89128
}
90129

91130
const WRAPPED_MINT_BACKPOINTER_SEED: &[u8] = br"backpointer";
@@ -107,16 +146,32 @@ pub(crate) fn get_wrapped_mint_backpointer_address_signer_seeds<'a>(
107146

108147
pub(crate) fn get_wrapped_mint_backpointer_address_with_seed(
109148
wrapped_mint: &Pubkey,
149+
) -> (Pubkey, u8) {
150+
get_wrapped_mint_backpointer_address_with_seed_for_program(wrapped_mint, &id())
151+
}
152+
153+
pub(crate) fn get_wrapped_mint_backpointer_address_with_seed_for_program(
154+
wrapped_mint: &Pubkey,
155+
program_id: &Pubkey,
110156
) -> (Pubkey, u8) {
111157
Pubkey::find_program_address(
112158
&get_wrapped_mint_backpointer_address_seeds(wrapped_mint),
113-
&id(),
159+
program_id,
114160
)
115161
}
116162

117163
/// Derive the SPL Token wrapped mint backpointer address
118164
pub fn get_wrapped_mint_backpointer_address(wrapped_mint: &Pubkey) -> Pubkey {
119-
get_wrapped_mint_backpointer_address_with_seed(wrapped_mint).0
165+
get_wrapped_mint_backpointer_address_for_program(wrapped_mint, &id())
166+
}
167+
168+
/// Derive the SPL Token wrapped mint backpointer address for a specific Token
169+
/// Wrap program deployment.
170+
pub fn get_wrapped_mint_backpointer_address_for_program(
171+
wrapped_mint: &Pubkey,
172+
program_id: &Pubkey,
173+
) -> Pubkey {
174+
get_wrapped_mint_backpointer_address_with_seed_for_program(wrapped_mint, program_id).0
120175
}
121176

122177
/// Derive the escrow `ATA` that backs a given wrapped mint.
@@ -125,12 +180,68 @@ pub fn get_escrow_address(
125180
unwrapped_token_program_id: &Pubkey,
126181
wrapped_token_program_id: &Pubkey,
127182
) -> Pubkey {
128-
let wrapped_mint = get_wrapped_mint_address(unwrapped_mint, wrapped_token_program_id);
129-
let mint_authority = get_wrapped_mint_authority(&wrapped_mint);
183+
get_escrow_address_for_program(
184+
unwrapped_mint,
185+
unwrapped_token_program_id,
186+
wrapped_token_program_id,
187+
&id(),
188+
)
189+
}
190+
191+
/// Derive the escrow `ATA` for a specific Token Wrap program deployment.
192+
pub fn get_escrow_address_for_program(
193+
unwrapped_mint: &Pubkey,
194+
unwrapped_token_program_id: &Pubkey,
195+
wrapped_token_program_id: &Pubkey,
196+
program_id: &Pubkey,
197+
) -> Pubkey {
198+
let wrapped_mint =
199+
get_wrapped_mint_address_for_program(unwrapped_mint, wrapped_token_program_id, program_id);
200+
let mint_authority = get_wrapped_mint_authority_for_program(&wrapped_mint, program_id);
130201

131202
get_associated_token_address_with_program_id(
132203
&mint_authority,
133204
unwrapped_mint,
134205
unwrapped_token_program_id,
135206
)
136207
}
208+
209+
const CANONICAL_POINTER_SEED: &[u8] = br"canonical_pointer";
210+
211+
/// Derives the canonical pointer address and bump seed for a specific
212+
/// Token Wrap program deployment.
213+
pub(crate) fn get_canonical_pointer_address_with_seed_for_program(
214+
unwrapped_mint: &Pubkey,
215+
program_id: &Pubkey,
216+
) -> (Pubkey, u8) {
217+
Pubkey::find_program_address(
218+
&[CANONICAL_POINTER_SEED, unwrapped_mint.as_ref()],
219+
program_id,
220+
)
221+
}
222+
223+
pub(crate) fn get_canonical_pointer_address_signer_seeds<'a>(
224+
unwrapped_mint: &'a Pubkey,
225+
bump_seed: &'a [u8],
226+
) -> [&'a [u8]; 3] {
227+
[CANONICAL_POINTER_SEED, unwrapped_mint.as_ref(), bump_seed]
228+
}
229+
230+
/// Derives the canonical pointer address and bump seed.
231+
pub(crate) fn get_canonical_pointer_address_with_seed(unwrapped_mint: &Pubkey) -> (Pubkey, u8) {
232+
get_canonical_pointer_address_with_seed_for_program(unwrapped_mint, &id())
233+
}
234+
235+
/// Derives the canonical pointer address for an unwrapped mint.
236+
pub fn get_canonical_pointer_address(unwrapped_mint: &Pubkey) -> Pubkey {
237+
get_canonical_pointer_address_for_program(unwrapped_mint, &id())
238+
}
239+
240+
/// Derives the canonical pointer address for an unwrapped mint for a specific
241+
/// Token Wrap program deployment.
242+
pub fn get_canonical_pointer_address_for_program(
243+
unwrapped_mint: &Pubkey,
244+
program_id: &Pubkey,
245+
) -> Pubkey {
246+
get_canonical_pointer_address_with_seed_for_program(unwrapped_mint, program_id).0
247+
}

program/src/processor.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use {
44
crate::{
55
error::TokenWrapError,
6+
get_canonical_pointer_address_signer_seeds, get_canonical_pointer_address_with_seed,
67
get_wrapped_mint_address, get_wrapped_mint_address_with_seed, get_wrapped_mint_authority,
78
get_wrapped_mint_authority_signer_seeds, get_wrapped_mint_authority_with_seed,
89
get_wrapped_mint_backpointer_address_signer_seeds,
@@ -13,7 +14,7 @@ use {
1314
mint_customizer::{
1415
default_token_2022::DefaultToken2022Customizer, interface::MintCustomizer,
1516
},
16-
state::Backpointer,
17+
state::{Backpointer, CanonicalDeploymentPointer},
1718
},
1819
mpl_token_metadata::{
1920
accounts::Metadata as MetaplexMetadata,
@@ -48,7 +49,7 @@ use {
4849
instruction::{initialize as initialize_token_metadata, remove_key, update_field},
4950
state::{Field, TokenMetadata},
5051
},
51-
std::collections::HashMap,
52+
std::{collections::HashMap, mem},
5253
};
5354

5455
/// Processes [`CreateMint`](enum.TokenWrapInstruction.html) instruction.
@@ -793,6 +794,91 @@ pub fn process_sync_metadata_to_spl_token(accounts: &[AccountInfo]) -> ProgramRe
793794
Ok(())
794795
}
795796

797+
/// Processes [`SetCanonicalPointer`](enum.TokenWrapInstruction.html)
798+
/// instruction.
799+
pub fn process_set_canonical_pointer(
800+
program_id: &Pubkey,
801+
accounts: &[AccountInfo],
802+
new_program_id: Pubkey,
803+
) -> ProgramResult {
804+
let account_info_iter = &mut accounts.iter();
805+
let unwrapped_mint_authority_info = next_account_info(account_info_iter)?;
806+
let canonical_pointer_info = next_account_info(account_info_iter)?;
807+
let unwrapped_mint_info = next_account_info(account_info_iter)?;
808+
let _system_program_info = next_account_info(account_info_iter)?;
809+
810+
if !unwrapped_mint_authority_info.is_signer {
811+
return Err(ProgramError::MissingRequiredSignature);
812+
}
813+
814+
if unwrapped_mint_info.owner != &spl_token::id()
815+
&& unwrapped_mint_info.owner != &spl_token_2022::id()
816+
{
817+
return Err(ProgramError::InvalidAccountOwner);
818+
}
819+
820+
let mint_data = unwrapped_mint_info.try_borrow_data()?;
821+
let mint_state = PodStateWithExtensions::<PodMint>::unpack(&mint_data)?;
822+
let mint_authority = mint_state
823+
.base
824+
.mint_authority
825+
.ok_or(ProgramError::InvalidAccountData)
826+
.inspect_err(|_| {
827+
msg!("Cannot create/update pointer for unwrapped mint if does not have an authority");
828+
})?;
829+
830+
if mint_authority != *unwrapped_mint_authority_info.key {
831+
return Err(ProgramError::IncorrectAuthority);
832+
}
833+
834+
let (expected_pointer_address, bump) =
835+
get_canonical_pointer_address_with_seed(unwrapped_mint_info.key);
836+
if *canonical_pointer_info.key != expected_pointer_address {
837+
msg!(
838+
"Error: canonical pointer address {} does not match expected address {}",
839+
canonical_pointer_info.key,
840+
expected_pointer_address
841+
);
842+
return Err(ProgramError::InvalidArgument);
843+
}
844+
845+
// If pointer does not exist, initialize it
846+
if canonical_pointer_info.data_is_empty() {
847+
let space = mem::size_of::<CanonicalDeploymentPointer>();
848+
let rent_required = Rent::get()?.minimum_balance(space);
849+
850+
if canonical_pointer_info.lamports() < rent_required {
851+
msg!(
852+
"Error: canonical pointer PDA requires pre-funding of {} lamports",
853+
rent_required
854+
);
855+
Err(ProgramError::AccountNotRentExempt)?
856+
}
857+
858+
let bump_seed = [bump];
859+
let signer_seeds =
860+
get_canonical_pointer_address_signer_seeds(unwrapped_mint_info.key, &bump_seed);
861+
invoke_signed(
862+
&allocate(canonical_pointer_info.key, space as u64),
863+
&[canonical_pointer_info.clone()],
864+
&[&signer_seeds],
865+
)?;
866+
invoke_signed(
867+
&assign(canonical_pointer_info.key, program_id),
868+
&[canonical_pointer_info.clone()],
869+
&[&signer_seeds],
870+
)?;
871+
}
872+
873+
// Set data within canonical pointer PDA
874+
875+
let mut pointer_data = canonical_pointer_info.try_borrow_mut_data()?;
876+
let state = bytemuck::from_bytes_mut::<CanonicalDeploymentPointer>(&mut pointer_data);
877+
state.program_id = new_program_id;
878+
879+
Ok(())
880+
}
881+
796882
/// Instruction processor
797883
pub fn process_instruction(
798884
program_id: &Pubkey,
@@ -826,5 +912,11 @@ pub fn process_instruction(
826912
msg!("Instruction: SyncMetadataToSplToken");
827913
process_sync_metadata_to_spl_token(accounts)
828914
}
915+
TokenWrapInstruction::SetCanonicalPointer {
916+
program_id: new_program_id,
917+
} => {
918+
msg!("Instruction: SetCanonicalPointer");
919+
process_set_canonical_pointer(program_id, accounts, new_program_id)
920+
}
829921
}
830922
}

0 commit comments

Comments
 (0)