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

Commit 57c6b07

Browse files
committed
token 2022: add UpdateGroupMaxSize instruction from SPL Token Group interface
1 parent 6b67ebc commit 57c6b07

File tree

3 files changed

+315
-1
lines changed

3 files changed

+315
-1
lines changed

token/client/src/token.rs

+21
Original file line numberDiff line numberDiff line change
@@ -3920,4 +3920,25 @@ where
39203920
));
39213921
self.process_ixs(&instructions, signing_keypairs).await
39223922
}
3923+
3924+
/// Update a token-group max size on a mint
3925+
pub async fn token_group_update_max_size<S: Signers>(
3926+
&self,
3927+
update_authority: &Pubkey,
3928+
new_max_size: u32,
3929+
signing_keypairs: &S,
3930+
) -> TokenResult<T::Output> {
3931+
self.process_ixs(
3932+
&[
3933+
spl_token_group_interface::instruction::update_group_max_size(
3934+
&self.program_id,
3935+
&self.pubkey,
3936+
update_authority,
3937+
new_max_size,
3938+
),
3939+
],
3940+
signing_keypairs,
3941+
)
3942+
.await
3943+
}
39233944
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#![cfg(feature = "test-sbf")]
2+
#![allow(clippy::items_after_test_module)]
3+
4+
mod program_test;
5+
use {
6+
program_test::TestContext,
7+
solana_program_test::{processor, tokio, ProgramTest},
8+
solana_sdk::{
9+
account::Account as SolanaAccount, instruction::InstructionError, pubkey::Pubkey,
10+
signature::Signer, signer::keypair::Keypair, transaction::TransactionError,
11+
transport::TransportError,
12+
},
13+
spl_token_2022::{extension::BaseStateWithExtensions, processor::Processor},
14+
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
15+
spl_token_group_interface::{
16+
error::TokenGroupError, instruction::update_group_max_size, state::TokenGroup,
17+
},
18+
std::{convert::TryInto, sync::Arc},
19+
test_case::test_case,
20+
};
21+
22+
fn setup_program_test() -> ProgramTest {
23+
let mut program_test = ProgramTest::default();
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, 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+
let group_address = Some(mint.pubkey());
42+
context
43+
.init_token_with_mint_keypair_and_freeze_authority(
44+
mint,
45+
vec![ExtensionInitializationParams::GroupPointer {
46+
authority: Some(*authority),
47+
group_address,
48+
}],
49+
None,
50+
)
51+
.await
52+
.unwrap();
53+
context
54+
}
55+
56+
// Successful attempts to set higher than size
57+
#[test_case(0, 0, 10)]
58+
#[test_case(5, 0, 10)]
59+
#[test_case(50, 0, 200_000)]
60+
#[test_case(100_000, 100_000, 200_000)]
61+
#[test_case(50, 0, 300_000_000)]
62+
#[test_case(100_000, 100_000, 300_000_000)]
63+
#[test_case(100_000_000, 100_000_000, 300_000_000)]
64+
#[test_case(0, 0, u32::MAX)]
65+
#[test_case(200_000, 200_000, u32::MAX)]
66+
#[test_case(300_000_000, 300_000_000, u32::MAX)]
67+
// Attempts to set lower than size
68+
#[test_case(5, 5, 4)]
69+
#[test_case(200_000, 200_000, 50)]
70+
#[test_case(200_000, 200_000, 100_000)]
71+
#[test_case(300_000_000, 300_000_000, 50)]
72+
#[test_case(u32::MAX, u32::MAX, 0)]
73+
#[tokio::test]
74+
async fn test_update_group_max_size(max_size: u32, size: u32, new_max_size: u32) {
75+
let authority = Keypair::new();
76+
let mint_keypair = Keypair::new();
77+
let mut test_context = setup(mint_keypair.insecure_clone(), &authority.pubkey()).await;
78+
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
79+
let token_context = test_context.token_context.take().unwrap();
80+
81+
let update_authority = Keypair::new();
82+
let mut token_group = TokenGroup::new(
83+
&mint_keypair.pubkey(),
84+
Some(update_authority.pubkey()).try_into().unwrap(),
85+
max_size,
86+
);
87+
88+
token_context
89+
.token
90+
.token_group_initialize_with_rent_transfer(
91+
&payer_pubkey,
92+
&token_context.mint_authority.pubkey(),
93+
&update_authority.pubkey(),
94+
max_size,
95+
&[&token_context.mint_authority],
96+
)
97+
.await
98+
.unwrap();
99+
100+
{
101+
// Update the group's size manually
102+
let mut context = test_context.context.lock().await;
103+
104+
let group_mint_account = context
105+
.banks_client
106+
.get_account(mint_keypair.pubkey())
107+
.await
108+
.unwrap()
109+
.unwrap();
110+
111+
let old_data = context
112+
.banks_client
113+
.get_account(mint_keypair.pubkey())
114+
.await
115+
.unwrap()
116+
.unwrap()
117+
.data;
118+
119+
let data = {
120+
// 0....81: mint
121+
// 82...164: padding
122+
// 165..166: account type
123+
// 167..170: extension discriminator (GroupPointer)
124+
// 171..202: authority
125+
// 203..234: group pointer
126+
// 235..238: extension discriminator (TokenGroup)
127+
// 239..270: mint
128+
// 271..302: update_authority
129+
// 303..306: size
130+
// 307..310: max_size
131+
let (front, back) = old_data.split_at(302);
132+
let (_, back) = back.split_at(3);
133+
let size_bytes = size.to_le_bytes();
134+
let mut bytes = vec![];
135+
bytes.extend_from_slice(front);
136+
bytes.extend_from_slice(&size_bytes);
137+
bytes.extend_from_slice(back);
138+
bytes
139+
};
140+
141+
context.set_account(
142+
&mint_keypair.pubkey(),
143+
&SolanaAccount {
144+
data,
145+
..group_mint_account
146+
}
147+
.into(),
148+
);
149+
150+
token_group.size = size.into();
151+
}
152+
153+
token_group.max_size = new_max_size.into();
154+
155+
if new_max_size < size {
156+
let error = token_context
157+
.token
158+
.token_group_update_max_size(
159+
&update_authority.pubkey(),
160+
new_max_size,
161+
&[&update_authority],
162+
)
163+
.await
164+
.unwrap_err();
165+
assert_eq!(
166+
error,
167+
TokenClientError::Client(Box::new(TransportError::TransactionError(
168+
TransactionError::InstructionError(
169+
0,
170+
InstructionError::Custom(TokenGroupError::SizeExceedsNewMaxSize as u32)
171+
)
172+
))),
173+
);
174+
} else {
175+
token_context
176+
.token
177+
.token_group_update_max_size(
178+
&update_authority.pubkey(),
179+
new_max_size,
180+
&[&update_authority],
181+
)
182+
.await
183+
.unwrap();
184+
185+
let mint_info = token_context.token.get_mint_info().await.unwrap();
186+
let fetched_group = mint_info.get_extension::<TokenGroup>().unwrap();
187+
assert_eq!(fetched_group, &token_group);
188+
}
189+
}
190+
191+
#[tokio::test]
192+
async fn fail_authority_checks() {
193+
let authority = Keypair::new();
194+
let mint_keypair = Keypair::new();
195+
let mut test_context = setup(mint_keypair, &authority.pubkey()).await;
196+
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
197+
let token_context = test_context.token_context.take().unwrap();
198+
199+
let update_authority = Keypair::new();
200+
token_context
201+
.token
202+
.token_group_initialize_with_rent_transfer(
203+
&payer_pubkey,
204+
&token_context.mint_authority.pubkey(),
205+
&update_authority.pubkey(),
206+
10,
207+
&[&token_context.mint_authority],
208+
)
209+
.await
210+
.unwrap();
211+
212+
// no signature
213+
let mut instruction = update_group_max_size(
214+
&spl_token_2022::id(),
215+
token_context.token.get_address(),
216+
&update_authority.pubkey(),
217+
20,
218+
);
219+
instruction.accounts[1].is_signer = false;
220+
221+
let error = token_context
222+
.token
223+
.process_ixs(&[instruction], &[] as &[&dyn Signer; 0]) // yuck, but the compiler needs it
224+
.await
225+
.unwrap_err();
226+
assert_eq!(
227+
error,
228+
TokenClientError::Client(Box::new(TransportError::TransactionError(
229+
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
230+
)))
231+
);
232+
233+
// wrong authority
234+
let wrong_authority = Keypair::new();
235+
let error = token_context
236+
.token
237+
.token_group_update_max_size(&wrong_authority.pubkey(), 20, &[&wrong_authority])
238+
.await
239+
.unwrap_err();
240+
assert_eq!(
241+
error,
242+
TokenClientError::Client(Box::new(TransportError::TransactionError(
243+
TransactionError::InstructionError(
244+
0,
245+
InstructionError::Custom(TokenGroupError::IncorrectUpdateAuthority as u32)
246+
)
247+
)))
248+
);
249+
}

token/program-2022/src/extension/token_group/processor.rs

+45-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ use {
1818
program_option::COption,
1919
pubkey::Pubkey,
2020
},
21+
spl_pod::optional_keys::OptionalNonZeroPubkey,
2122
spl_token_group_interface::{
2223
error::TokenGroupError,
23-
instruction::{InitializeGroup, TokenGroupInstruction},
24+
instruction::{InitializeGroup, TokenGroupInstruction, UpdateGroupMaxSize},
2425
state::TokenGroup,
2526
},
2627
};
@@ -35,6 +36,21 @@ fn realloc_mint(mint_info: &AccountInfo, extension: ExtensionType) -> Result<(),
3536
Ok(())
3637
}
3738

39+
fn check_update_authority(
40+
update_authority_info: &AccountInfo,
41+
expected_update_authority: &OptionalNonZeroPubkey,
42+
) -> Result<(), ProgramError> {
43+
if !update_authority_info.is_signer {
44+
return Err(ProgramError::MissingRequiredSignature);
45+
}
46+
let update_authority = Option::<Pubkey>::from(*expected_update_authority)
47+
.ok_or(TokenGroupError::ImmutableGroup)?;
48+
if update_authority != *update_authority_info.key {
49+
return Err(TokenGroupError::IncorrectUpdateAuthority.into());
50+
}
51+
Ok(())
52+
}
53+
3854
/// Processes a [InitializeGroup](enum.TokenGroupInstruction.html) instruction.
3955
pub fn process_initialize_group(
4056
_program_id: &Pubkey,
@@ -91,6 +107,30 @@ pub fn process_initialize_group(
91107
Ok(())
92108
}
93109

110+
/// Processes an
111+
/// [UpdateGroupMaxSize](enum.GroupInterfaceInstruction.html)
112+
/// instruction
113+
pub fn process_update_group_max_size(
114+
_program_id: &Pubkey,
115+
accounts: &[AccountInfo],
116+
data: UpdateGroupMaxSize,
117+
) -> ProgramResult {
118+
let account_info_iter = &mut accounts.iter();
119+
120+
let group_info = next_account_info(account_info_iter)?;
121+
let update_authority_info = next_account_info(account_info_iter)?;
122+
123+
let mut buffer = group_info.try_borrow_mut_data()?;
124+
let mut state = StateWithExtensionsMut::<Mint>::unpack(&mut buffer)?;
125+
let group = state.get_extension_mut::<TokenGroup>()?;
126+
127+
check_update_authority(update_authority_info, &group.update_authority)?;
128+
129+
group.update_max_size(data.max_size.into())?;
130+
131+
Ok(())
132+
}
133+
94134
/// Processes an [Instruction](enum.Instruction.html).
95135
pub fn process_instruction(
96136
program_id: &Pubkey,
@@ -102,6 +142,10 @@ pub fn process_instruction(
102142
msg!("TokenGroupInstruction: InitializeGroup");
103143
process_initialize_group(program_id, accounts, data)
104144
}
145+
TokenGroupInstruction::UpdateGroupMaxSize(data) => {
146+
msg!("TokenGroupInstruction: UpdateGroupMaxSize");
147+
process_update_group_max_size(program_id, accounts, data)
148+
}
105149
_ => Err(ProgramError::InvalidInstructionData),
106150
}
107151
}

0 commit comments

Comments
 (0)