Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 9df673b

Browse files
authored
token-2022: Add metadata pointer extension (#4549)
1 parent 307d10e commit 9df673b

File tree

9 files changed

+599
-4
lines changed

9 files changed

+599
-4
lines changed

token/cli/src/main.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use spl_token_2022::{
4343
default_account_state::DefaultAccountState,
4444
interest_bearing_mint::InterestBearingConfig,
4545
memo_transfer::MemoTransfer,
46+
metadata_pointer::MetadataPointer,
4647
mint_close_authority::MintCloseAuthority,
4748
permanent_delegate::PermanentDelegate,
4849
transfer_fee::{TransferFeeAmount, TransferFeeConfig},
@@ -783,6 +784,7 @@ async fn command_authorize(
783784
AuthorityType::ConfidentialTransferFeeConfig => {
784785
"confidential transfer fee config authority"
785786
}
787+
AuthorityType::MetadataPointer => "metadata pointer authority",
786788
};
787789

788790
let (mint_pubkey, previous_authority) = if !config.sign_only {
@@ -884,6 +886,16 @@ async fn command_authorize(
884886
))
885887
}
886888
}
889+
AuthorityType::MetadataPointer => {
890+
if let Ok(extension) = mint.get_extension::<MetadataPointer>() {
891+
Ok(COption::<Pubkey>::from(extension.authority))
892+
} else {
893+
Err(format!(
894+
"Mint `{}` does not support a metadata pointer",
895+
account
896+
))
897+
}
898+
}
887899
}?;
888900

889901
Ok((account, previous_authority))
@@ -920,7 +932,8 @@ async fn command_authorize(
920932
| AuthorityType::PermanentDelegate
921933
| AuthorityType::ConfidentialTransferMint
922934
| AuthorityType::TransferHookProgramId
923-
| AuthorityType::ConfidentialTransferFeeConfig => Err(format!(
935+
| AuthorityType::ConfidentialTransferFeeConfig
936+
| AuthorityType::MetadataPointer => Err(format!(
924937
"Authority type `{}` not supported for SPL Token accounts",
925938
auth_str
926939
)),
@@ -3884,6 +3897,8 @@ async fn process_command<'a>(
38843897
"permanent-delegate" => AuthorityType::PermanentDelegate,
38853898
"confidential-transfer-mint" => AuthorityType::ConfidentialTransferMint,
38863899
"transfer-hook-program-id" => AuthorityType::TransferHookProgramId,
3900+
"confidential-transfer-fee" => AuthorityType::ConfidentialTransferFeeConfig,
3901+
"metadata-pointer" => AuthorityType::MetadataPointer,
38873902
_ => unreachable!(),
38883903
};
38893904

token/client/src/token.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use {
2121
spl_token_2022::{
2222
extension::{
2323
confidential_transfer, cpi_guard, default_account_state, interest_bearing_mint,
24-
memo_transfer, transfer_fee, transfer_hook, BaseStateWithExtensions, ExtensionType,
25-
StateWithExtensionsOwned,
24+
memo_transfer, metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions,
25+
ExtensionType, StateWithExtensionsOwned,
2626
},
2727
instruction, offchain,
2828
pod::EncryptionPubkey,
@@ -137,6 +137,10 @@ pub enum ExtensionInitializationParams {
137137
authority: Option<Pubkey>,
138138
program_id: Option<Pubkey>,
139139
},
140+
MetadataPointer {
141+
authority: Option<Pubkey>,
142+
metadata_address: Option<Pubkey>,
143+
},
140144
}
141145
impl ExtensionInitializationParams {
142146
/// Get the extension type associated with the init params
@@ -150,6 +154,7 @@ impl ExtensionInitializationParams {
150154
Self::NonTransferable => ExtensionType::NonTransferable,
151155
Self::PermanentDelegate { .. } => ExtensionType::PermanentDelegate,
152156
Self::TransferHook { .. } => ExtensionType::TransferHook,
157+
Self::MetadataPointer { .. } => ExtensionType::MetadataPointer,
153158
}
154159
}
155160
/// Generate an appropriate initialization instruction for the given mint
@@ -221,6 +226,15 @@ impl ExtensionInitializationParams {
221226
authority,
222227
program_id,
223228
),
229+
Self::MetadataPointer {
230+
authority,
231+
metadata_address,
232+
} => metadata_pointer::instruction::initialize(
233+
token_program_id,
234+
mint,
235+
authority,
236+
metadata_address,
237+
),
224238
}
225239
}
226240
}
@@ -1559,6 +1573,29 @@ where
15591573
.await
15601574
}
15611575

1576+
/// Update metadata pointer address
1577+
pub async fn update_metadata_address<S: Signers>(
1578+
&self,
1579+
authority: &Pubkey,
1580+
new_metadata_address: Option<Pubkey>,
1581+
signing_keypairs: &S,
1582+
) -> TokenResult<T::Output> {
1583+
let signing_pubkeys = signing_keypairs.pubkeys();
1584+
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1585+
1586+
self.process_ixs(
1587+
&[metadata_pointer::instruction::update(
1588+
&self.program_id,
1589+
self.get_address(),
1590+
authority,
1591+
&multisig_signers,
1592+
new_metadata_address,
1593+
)?],
1594+
signing_keypairs,
1595+
)
1596+
.await
1597+
}
1598+
15621599
/// Update confidential transfer mint
15631600
pub async fn confidential_transfer_update_mint<S: Signer>(
15641601
&self,
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#![cfg(feature = "test-sbf")]
2+
3+
mod program_test;
4+
use {
5+
program_test::TestContext,
6+
solana_program_test::{processor, tokio, ProgramTest},
7+
solana_sdk::{
8+
instruction::InstructionError, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair,
9+
transaction::TransactionError, transport::TransportError,
10+
},
11+
spl_token_2022::{
12+
error::TokenError,
13+
extension::{metadata_pointer::MetadataPointer, BaseStateWithExtensions},
14+
instruction,
15+
processor::Processor,
16+
},
17+
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
18+
std::{convert::TryInto, sync::Arc},
19+
};
20+
21+
fn setup_program_test() -> ProgramTest {
22+
let mut program_test = ProgramTest::default();
23+
program_test.prefer_bpf(false);
24+
program_test.add_program(
25+
"spl_token_2022",
26+
spl_token_2022::id(),
27+
processor!(Processor::process),
28+
);
29+
program_test
30+
}
31+
32+
async fn setup(mint: Keypair, metadata_address: &Pubkey, authority: &Pubkey) -> TestContext {
33+
let program_test = setup_program_test();
34+
35+
let context = program_test.start_with_context().await;
36+
let context = Arc::new(tokio::sync::Mutex::new(context));
37+
let mut context = TestContext {
38+
context,
39+
token_context: None,
40+
};
41+
context
42+
.init_token_with_mint_keypair_and_freeze_authority(
43+
mint,
44+
vec![ExtensionInitializationParams::MetadataPointer {
45+
authority: Some(*authority),
46+
metadata_address: Some(*metadata_address),
47+
}],
48+
None,
49+
)
50+
.await
51+
.unwrap();
52+
context
53+
}
54+
55+
#[tokio::test]
56+
async fn success_init() {
57+
let authority = Pubkey::new_unique();
58+
let metadata_address = Pubkey::new_unique();
59+
let mint_keypair = Keypair::new();
60+
let token = setup(mint_keypair, &metadata_address, &authority)
61+
.await
62+
.token_context
63+
.take()
64+
.unwrap()
65+
.token;
66+
67+
let state = token.get_mint_info().await.unwrap();
68+
assert!(state.base.is_initialized);
69+
let extension = state.get_extension::<MetadataPointer>().unwrap();
70+
assert_eq!(extension.authority, Some(authority).try_into().unwrap());
71+
assert_eq!(
72+
extension.metadata_address,
73+
Some(metadata_address).try_into().unwrap()
74+
);
75+
}
76+
77+
#[tokio::test]
78+
async fn fail_init_all_none() {
79+
let mut program_test = ProgramTest::default();
80+
program_test.prefer_bpf(false);
81+
program_test.add_program(
82+
"spl_token_2022",
83+
spl_token_2022::id(),
84+
processor!(Processor::process),
85+
);
86+
let context = program_test.start_with_context().await;
87+
let context = Arc::new(tokio::sync::Mutex::new(context));
88+
let mut context = TestContext {
89+
context,
90+
token_context: None,
91+
};
92+
let err = context
93+
.init_token_with_mint_keypair_and_freeze_authority(
94+
Keypair::new(),
95+
vec![ExtensionInitializationParams::MetadataPointer {
96+
authority: None,
97+
metadata_address: None,
98+
}],
99+
None,
100+
)
101+
.await
102+
.unwrap_err();
103+
assert_eq!(
104+
err,
105+
TokenClientError::Client(Box::new(TransportError::TransactionError(
106+
TransactionError::InstructionError(
107+
1,
108+
InstructionError::Custom(TokenError::InvalidInstruction as u32)
109+
)
110+
)))
111+
);
112+
}
113+
114+
#[tokio::test]
115+
async fn set_authority() {
116+
let authority = Keypair::new();
117+
let metadata_address = Pubkey::new_unique();
118+
let mint_keypair = Keypair::new();
119+
let token = setup(mint_keypair, &metadata_address, &authority.pubkey())
120+
.await
121+
.token_context
122+
.take()
123+
.unwrap()
124+
.token;
125+
let new_authority = Keypair::new();
126+
127+
// fail, wrong signature
128+
let wrong = Keypair::new();
129+
let err = token
130+
.set_authority(
131+
token.get_address(),
132+
&wrong.pubkey(),
133+
Some(&new_authority.pubkey()),
134+
instruction::AuthorityType::MetadataPointer,
135+
&[&wrong],
136+
)
137+
.await
138+
.unwrap_err();
139+
assert_eq!(
140+
err,
141+
TokenClientError::Client(Box::new(TransportError::TransactionError(
142+
TransactionError::InstructionError(
143+
0,
144+
InstructionError::Custom(TokenError::OwnerMismatch as u32)
145+
)
146+
)))
147+
);
148+
149+
// success
150+
token
151+
.set_authority(
152+
token.get_address(),
153+
&authority.pubkey(),
154+
Some(&new_authority.pubkey()),
155+
instruction::AuthorityType::MetadataPointer,
156+
&[&authority],
157+
)
158+
.await
159+
.unwrap();
160+
let state = token.get_mint_info().await.unwrap();
161+
let extension = state.get_extension::<MetadataPointer>().unwrap();
162+
assert_eq!(
163+
extension.authority,
164+
Some(new_authority.pubkey()).try_into().unwrap(),
165+
);
166+
167+
// set to none
168+
token
169+
.set_authority(
170+
token.get_address(),
171+
&new_authority.pubkey(),
172+
None,
173+
instruction::AuthorityType::MetadataPointer,
174+
&[&new_authority],
175+
)
176+
.await
177+
.unwrap();
178+
let state = token.get_mint_info().await.unwrap();
179+
let extension = state.get_extension::<MetadataPointer>().unwrap();
180+
assert_eq!(extension.authority, None.try_into().unwrap(),);
181+
182+
// fail set again
183+
let err = token
184+
.set_authority(
185+
token.get_address(),
186+
&new_authority.pubkey(),
187+
Some(&authority.pubkey()),
188+
instruction::AuthorityType::MetadataPointer,
189+
&[&new_authority],
190+
)
191+
.await
192+
.unwrap_err();
193+
assert_eq!(
194+
err,
195+
TokenClientError::Client(Box::new(TransportError::TransactionError(
196+
TransactionError::InstructionError(
197+
0,
198+
InstructionError::Custom(TokenError::AuthorityTypeNotSupported as u32)
199+
)
200+
)))
201+
);
202+
}
203+
204+
#[tokio::test]
205+
async fn update_metadata_address() {
206+
let authority = Keypair::new();
207+
let metadata_address = Pubkey::new_unique();
208+
let mint_keypair = Keypair::new();
209+
let token = setup(mint_keypair, &metadata_address, &authority.pubkey())
210+
.await
211+
.token_context
212+
.take()
213+
.unwrap()
214+
.token;
215+
let new_metadata_address = Pubkey::new_unique();
216+
217+
// fail, wrong signature
218+
let wrong = Keypair::new();
219+
let err = token
220+
.update_metadata_address(&wrong.pubkey(), Some(new_metadata_address), &[&wrong])
221+
.await
222+
.unwrap_err();
223+
assert_eq!(
224+
err,
225+
TokenClientError::Client(Box::new(TransportError::TransactionError(
226+
TransactionError::InstructionError(
227+
0,
228+
InstructionError::Custom(TokenError::OwnerMismatch as u32)
229+
)
230+
)))
231+
);
232+
233+
// success
234+
token
235+
.update_metadata_address(
236+
&authority.pubkey(),
237+
Some(new_metadata_address),
238+
&[&authority],
239+
)
240+
.await
241+
.unwrap();
242+
let state = token.get_mint_info().await.unwrap();
243+
let extension = state.get_extension::<MetadataPointer>().unwrap();
244+
assert_eq!(
245+
extension.metadata_address,
246+
Some(new_metadata_address).try_into().unwrap(),
247+
);
248+
249+
// set to none
250+
token
251+
.update_metadata_address(&authority.pubkey(), None, &[&authority])
252+
.await
253+
.unwrap();
254+
let state = token.get_mint_info().await.unwrap();
255+
let extension = state.get_extension::<MetadataPointer>().unwrap();
256+
assert_eq!(extension.metadata_address, None.try_into().unwrap(),);
257+
}

0 commit comments

Comments
 (0)