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

Commit e52e102

Browse files
committed
token 2022: add GroupMemberPointer extension
1 parent 2c99ff5 commit e52e102

File tree

8 files changed

+599
-4
lines changed

8 files changed

+599
-4
lines changed

token/client/src/token.rs

+40-3
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ use {
4040
self, account_info::WithheldTokensInfo, ConfidentialTransferFeeAmount,
4141
ConfidentialTransferFeeConfig,
4242
},
43-
cpi_guard, default_account_state, group_pointer, interest_bearing_mint, memo_transfer,
44-
metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions, ExtensionType,
45-
StateWithExtensionsOwned,
43+
cpi_guard, default_account_state, group_member_pointer, group_pointer,
44+
interest_bearing_mint, memo_transfer, metadata_pointer, transfer_fee, transfer_hook,
45+
BaseStateWithExtensions, ExtensionType, StateWithExtensionsOwned,
4646
},
4747
instruction, offchain,
4848
proof::ProofLocation,
@@ -182,6 +182,10 @@ pub enum ExtensionInitializationParams {
182182
authority: Option<Pubkey>,
183183
group_address: Option<Pubkey>,
184184
},
185+
GroupMemberPointer {
186+
authority: Option<Pubkey>,
187+
member_address: Option<Pubkey>,
188+
},
185189
}
186190
impl ExtensionInitializationParams {
187191
/// Get the extension type associated with the init params
@@ -200,6 +204,7 @@ impl ExtensionInitializationParams {
200204
ExtensionType::ConfidentialTransferFeeConfig
201205
}
202206
Self::GroupPointer { .. } => ExtensionType::GroupPointer,
207+
Self::GroupMemberPointer { .. } => ExtensionType::GroupMemberPointer,
203208
}
204209
}
205210
/// Generate an appropriate initialization instruction for the given mint
@@ -300,6 +305,15 @@ impl ExtensionInitializationParams {
300305
authority,
301306
group_address,
302307
),
308+
Self::GroupMemberPointer {
309+
authority,
310+
member_address,
311+
} => group_member_pointer::instruction::initialize(
312+
token_program_id,
313+
mint,
314+
authority,
315+
member_address,
316+
),
303317
}
304318
}
305319
}
@@ -1703,6 +1717,29 @@ where
17031717
.await
17041718
}
17051719

1720+
/// Update group member pointer address
1721+
pub async fn update_group_member_address<S: Signers>(
1722+
&self,
1723+
authority: &Pubkey,
1724+
new_member_address: Option<Pubkey>,
1725+
signing_keypairs: &S,
1726+
) -> TokenResult<T::Output> {
1727+
let signing_pubkeys = signing_keypairs.pubkeys();
1728+
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1729+
1730+
self.process_ixs(
1731+
&[group_member_pointer::instruction::update(
1732+
&self.program_id,
1733+
self.get_address(),
1734+
authority,
1735+
&multisig_signers,
1736+
new_member_address,
1737+
)?],
1738+
signing_keypairs,
1739+
)
1740+
.await
1741+
}
1742+
17061743
/// Update confidential transfer mint
17071744
pub async fn confidential_transfer_update_mint<S: Signers>(
17081745
&self,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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::{group_member_pointer::GroupMemberPointer, 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, member_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::GroupMemberPointer {
45+
authority: Some(*authority),
46+
member_address: Some(*member_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 member_address = Pubkey::new_unique();
59+
let mint_keypair = Keypair::new();
60+
let token = setup(mint_keypair, &member_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::<GroupMemberPointer>().unwrap();
70+
assert_eq!(extension.authority, Some(authority).try_into().unwrap());
71+
assert_eq!(
72+
extension.member_address,
73+
Some(member_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::GroupMemberPointer {
96+
authority: None,
97+
member_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 member_address = Pubkey::new_unique();
118+
let mint_keypair = Keypair::new();
119+
let token = setup(mint_keypair, &member_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::GroupMemberPointer,
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::GroupMemberPointer,
156+
&[&authority],
157+
)
158+
.await
159+
.unwrap();
160+
let state = token.get_mint_info().await.unwrap();
161+
let extension = state.get_extension::<GroupMemberPointer>().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::GroupMemberPointer,
174+
&[&new_authority],
175+
)
176+
.await
177+
.unwrap();
178+
let state = token.get_mint_info().await.unwrap();
179+
let extension = state.get_extension::<GroupMemberPointer>().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::GroupMemberPointer,
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_group_member_address() {
206+
let authority = Keypair::new();
207+
let member_address = Pubkey::new_unique();
208+
let mint_keypair = Keypair::new();
209+
let token = setup(mint_keypair, &member_address, &authority.pubkey())
210+
.await
211+
.token_context
212+
.take()
213+
.unwrap()
214+
.token;
215+
let new_member_address = Pubkey::new_unique();
216+
217+
// fail, wrong signature
218+
let wrong = Keypair::new();
219+
let err = token
220+
.update_group_member_address(&wrong.pubkey(), Some(new_member_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_group_member_address(&authority.pubkey(), Some(new_member_address), &[&authority])
236+
.await
237+
.unwrap();
238+
let state = token.get_mint_info().await.unwrap();
239+
let extension = state.get_extension::<GroupMemberPointer>().unwrap();
240+
assert_eq!(
241+
extension.member_address,
242+
Some(new_member_address).try_into().unwrap(),
243+
);
244+
245+
// set to none
246+
token
247+
.update_group_member_address(&authority.pubkey(), None, &[&authority])
248+
.await
249+
.unwrap();
250+
let state = token.get_mint_info().await.unwrap();
251+
let extension = state.get_extension::<GroupMemberPointer>().unwrap();
252+
assert_eq!(extension.member_address, None.try_into().unwrap(),);
253+
}

0 commit comments

Comments
 (0)