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

Commit 5b340e5

Browse files
committed
token 2022: add InitializeGroup instruction from SPL Token Group interface
1 parent 82f3418 commit 5b340e5

File tree

7 files changed

+392
-1
lines changed

7 files changed

+392
-1
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
#![cfg(feature = "test-sbf")]
2+
3+
mod program_test;
4+
use {
5+
borsh::BorshDeserialize,
6+
program_test::TestContext,
7+
solana_program_test::{processor, tokio, ProgramTest},
8+
solana_sdk::{
9+
instruction::InstructionError, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair,
10+
transaction::TransactionError, transport::TransportError,
11+
},
12+
spl_token_2022::{error::TokenError, extension::BaseStateWithExtensions, processor::Processor},
13+
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
14+
spl_token_metadata_interface::state::TokenMetadata,
15+
std::{convert::TryInto, sync::Arc},
16+
};
17+
18+
fn setup_program_test() -> ProgramTest {
19+
let mut program_test = ProgramTest::default();
20+
program_test.add_program(
21+
"spl_token_2022",
22+
spl_token_2022::id(),
23+
processor!(Processor::process),
24+
);
25+
program_test
26+
}
27+
28+
async fn setup(mint: Keypair, authority: &Pubkey) -> TestContext {
29+
let program_test = setup_program_test();
30+
31+
let context = program_test.start_with_context().await;
32+
let context = Arc::new(tokio::sync::Mutex::new(context));
33+
let mut context = TestContext {
34+
context,
35+
token_context: None,
36+
};
37+
let metadata_address = Some(mint.pubkey());
38+
context
39+
.init_token_with_mint_keypair_and_freeze_authority(
40+
mint,
41+
vec![ExtensionInitializationParams::MetadataPointer {
42+
authority: Some(*authority),
43+
metadata_address,
44+
}],
45+
None,
46+
)
47+
.await
48+
.unwrap();
49+
context
50+
}
51+
52+
#[tokio::test]
53+
async fn success_initialize_group() {
54+
let group = Keypair::new();
55+
let group_mint = Keypair::new();
56+
let group_mint_authority = Keypair::new();
57+
58+
let mut test_context = setup(group_mint, &group_mint_authority).await;
59+
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
60+
let token_context = test_context.token_context.take().unwrap();
61+
62+
//
63+
64+
// fails without more lamports for new rent-exemption
65+
let error = token_context
66+
.token
67+
.token_metadata_initialize(
68+
&update_authority,
69+
&token_context.mint_authority.pubkey(),
70+
token_metadata.name.clone(),
71+
token_metadata.symbol.clone(),
72+
token_metadata.uri.clone(),
73+
&[&token_context.mint_authority],
74+
)
75+
.await
76+
.unwrap_err();
77+
assert_eq!(
78+
error,
79+
TokenClientError::Client(Box::new(TransportError::TransactionError(
80+
TransactionError::InsufficientFundsForRent { account_index: 2 }
81+
)))
82+
);
83+
84+
// fail wrong signer
85+
let not_mint_authority = Keypair::new();
86+
let error = token_context
87+
.token
88+
.token_metadata_initialize_with_rent_transfer(
89+
&payer_pubkey,
90+
&update_authority,
91+
&not_mint_authority.pubkey(),
92+
token_metadata.name.clone(),
93+
token_metadata.symbol.clone(),
94+
token_metadata.uri.clone(),
95+
&[&not_mint_authority],
96+
)
97+
.await
98+
.unwrap_err();
99+
assert_eq!(
100+
error,
101+
TokenClientError::Client(Box::new(TransportError::TransactionError(
102+
TransactionError::InstructionError(
103+
1,
104+
InstructionError::Custom(TokenError::IncorrectMintAuthority as u32)
105+
)
106+
)))
107+
);
108+
109+
token_context
110+
.token
111+
.token_metadata_initialize_with_rent_transfer(
112+
&payer_pubkey,
113+
&update_authority,
114+
&token_context.mint_authority.pubkey(),
115+
token_metadata.name.clone(),
116+
token_metadata.symbol.clone(),
117+
token_metadata.uri.clone(),
118+
&[&token_context.mint_authority],
119+
)
120+
.await
121+
.unwrap();
122+
123+
// check that the data is correct
124+
let mint_info = token_context.token.get_mint_info().await.unwrap();
125+
let metadata_bytes = mint_info.get_extension_bytes::<TokenMetadata>().unwrap();
126+
let fetched_metadata = TokenMetadata::try_from_slice(metadata_bytes).unwrap();
127+
assert_eq!(fetched_metadata, token_metadata);
128+
129+
// fail double-init
130+
let error = token_context
131+
.token
132+
.token_metadata_initialize_with_rent_transfer(
133+
&payer_pubkey,
134+
&update_authority,
135+
&token_context.mint_authority.pubkey(),
136+
token_metadata.name.clone(),
137+
token_metadata.symbol.clone(),
138+
token_metadata.uri.clone(),
139+
&[&token_context.mint_authority],
140+
)
141+
.await
142+
.unwrap_err();
143+
assert_eq!(
144+
error,
145+
TokenClientError::Client(Box::new(TransportError::TransactionError(
146+
TransactionError::InstructionError(
147+
0,
148+
InstructionError::Custom(TokenError::ExtensionAlreadyInitialized as u32)
149+
)
150+
)))
151+
);
152+
}
153+
154+
#[tokio::test]
155+
async fn fail_without_metadata_pointer() {
156+
let mut test_context = {
157+
let mint_keypair = Keypair::new();
158+
let program_test = setup_program_test();
159+
let context = program_test.start_with_context().await;
160+
let context = Arc::new(tokio::sync::Mutex::new(context));
161+
let mut context = TestContext {
162+
context,
163+
token_context: None,
164+
};
165+
context
166+
.init_token_with_mint_keypair_and_freeze_authority(mint_keypair, vec![], None)
167+
.await
168+
.unwrap();
169+
context
170+
};
171+
172+
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
173+
let token_context = test_context.token_context.take().unwrap();
174+
175+
let error = token_context
176+
.token
177+
.token_metadata_initialize_with_rent_transfer(
178+
&payer_pubkey,
179+
&Pubkey::new_unique(),
180+
&token_context.mint_authority.pubkey(),
181+
"Name".to_string(),
182+
"Symbol".to_string(),
183+
"URI".to_string(),
184+
&[&token_context.mint_authority],
185+
)
186+
.await
187+
.unwrap_err();
188+
assert_eq!(
189+
error,
190+
TokenClientError::Client(Box::new(TransportError::TransactionError(
191+
TransactionError::InstructionError(
192+
1,
193+
InstructionError::Custom(TokenError::InvalidExtensionCombination as u32)
194+
)
195+
)))
196+
);
197+
}
198+
199+
#[tokio::test]
200+
async fn fail_init_in_another_mint() {
201+
let authority = Pubkey::new_unique();
202+
let first_mint_keypair = Keypair::new();
203+
let first_mint = first_mint_keypair.pubkey();
204+
let mut test_context = setup(first_mint_keypair, &authority).await;
205+
let second_mint_keypair = Keypair::new();
206+
let second_mint = second_mint_keypair.pubkey();
207+
test_context
208+
.init_token_with_mint_keypair_and_freeze_authority(
209+
second_mint_keypair,
210+
vec![ExtensionInitializationParams::MetadataPointer {
211+
authority: Some(authority),
212+
metadata_address: Some(second_mint),
213+
}],
214+
None,
215+
)
216+
.await
217+
.unwrap();
218+
219+
let token_context = test_context.token_context.take().unwrap();
220+
221+
let error = token_context
222+
.token
223+
.process_ixs(
224+
&[spl_token_metadata_interface::instruction::initialize(
225+
&spl_token_2022::id(),
226+
&first_mint,
227+
&Pubkey::new_unique(),
228+
token_context.token.get_address(),
229+
&token_context.mint_authority.pubkey(),
230+
"Name".to_string(),
231+
"Symbol".to_string(),
232+
"URI".to_string(),
233+
)],
234+
&[&token_context.mint_authority],
235+
)
236+
.await
237+
.unwrap_err();
238+
239+
assert_eq!(
240+
error,
241+
TokenClientError::Client(Box::new(TransportError::TransactionError(
242+
TransactionError::InstructionError(
243+
0,
244+
InstructionError::Custom(TokenError::MintMismatch as u32)
245+
)
246+
)))
247+
);
248+
}
249+
250+
#[tokio::test]
251+
async fn fail_without_signature() {
252+
let authority = Pubkey::new_unique();
253+
let mint_keypair = Keypair::new();
254+
let mut test_context = setup(mint_keypair, &authority).await;
255+
256+
let token_context = test_context.token_context.take().unwrap();
257+
258+
let mut instruction = spl_token_metadata_interface::instruction::initialize(
259+
&spl_token_2022::id(),
260+
token_context.token.get_address(),
261+
&Pubkey::new_unique(),
262+
token_context.token.get_address(),
263+
&token_context.mint_authority.pubkey(),
264+
"Name".to_string(),
265+
"Symbol".to_string(),
266+
"URI".to_string(),
267+
);
268+
instruction.accounts[3].is_signer = false;
269+
let error = token_context
270+
.token
271+
.process_ixs(&[instruction], &[] as &[&dyn Signer; 0]) // yuck, but the compiler needs it
272+
.await
273+
.unwrap_err();
274+
275+
assert_eq!(
276+
error,
277+
TokenClientError::Client(Box::new(TransportError::TransactionError(
278+
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
279+
)))
280+
);
281+
}

token/program-2022/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ solana-program = "1.16.16"
2727
solana-zk-token-sdk = "1.16.16"
2828
spl-memo = { version = "4.0.0", path = "../../memo/program", features = [ "no-entrypoint" ] }
2929
spl-token = { version = "4.0", path = "../program", features = ["no-entrypoint"] }
30+
spl-token-group-interface = { version = "0.1.0", path = "../../token-group/interface" }
3031
spl-token-metadata-interface = { version = "0.2.0", path = "../../token-metadata/interface" }
3132
spl-transfer-hook-interface = { version = "0.3.0", path = "../transfer-hook-interface" }
3233
spl-type-length-value = { version = "0.3.0", path = "../../libraries/type-length-value" }

token/program-2022/src/extension/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use {
2929
program_error::ProgramError,
3030
program_pack::{IsInitialized, Pack},
3131
},
32+
spl_token_group_interface::state::TokenGroup,
3233
spl_pod::{
3334
bytemuck::{pod_from_bytes, pod_from_bytes_mut, pod_get_packed_len},
3435
primitives::PodU16,
@@ -68,6 +69,8 @@ pub mod non_transferable;
6869
pub mod permanent_delegate;
6970
/// Utility to reallocate token accounts
7071
pub mod reallocate;
72+
/// Token-group extension
73+
pub mod token_group;
7174
/// Token-metadata extension
7275
pub mod token_metadata;
7376
/// Transfer Fee extension
@@ -904,6 +907,8 @@ pub enum ExtensionType {
904907
ConfidentialTransferFeeAmount,
905908
/// Mint contains a pointer to another account (or the same account) that holds metadata
906909
MetadataPointer,
910+
/// Mint contains token group configurations
911+
TokenGroup,
907912
/// Mint contains token-metadata
908913
TokenMetadata,
909914
/// Test variable-length mint extension
@@ -979,6 +984,7 @@ impl ExtensionType {
979984
pod_get_packed_len::<ConfidentialTransferFeeAmount>()
980985
}
981986
ExtensionType::MetadataPointer => pod_get_packed_len::<MetadataPointer>(),
987+
ExtensionType::TokenGroup => pod_get_packed_len::<TokenGroup>(),
982988
ExtensionType::TokenMetadata => unreachable!(),
983989
#[cfg(test)]
984990
ExtensionType::AccountPaddingTest => pod_get_packed_len::<AccountPaddingTest>(),
@@ -1039,6 +1045,7 @@ impl ExtensionType {
10391045
| ExtensionType::TransferHook
10401046
| ExtensionType::ConfidentialTransferFeeConfig
10411047
| ExtensionType::MetadataPointer
1048+
| ExtensionType::TokenGroup
10421049
| ExtensionType::TokenMetadata => AccountType::Mint,
10431050
ExtensionType::ImmutableOwner
10441051
| ExtensionType::TransferFeeAmount
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use {
2+
crate::extension::{Extension, ExtensionType},
3+
spl_token_group_interface::state::TokenGroup,
4+
};
5+
6+
/// Instruction processor for the TokenGroup extensions
7+
pub mod processor;
8+
9+
impl Extension for TokenGroup {
10+
const TYPE: ExtensionType = ExtensionType::TokenGroup;
11+
}

0 commit comments

Comments
 (0)