Skip to content

Commit

Permalink
token 2022: add GroupPointer extension
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Oct 20, 2023
1 parent 6384308 commit c5d4211
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 2 deletions.
39 changes: 38 additions & 1 deletion token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use {
self, account_info::WithheldTokensInfo, ConfidentialTransferFeeAmount,
ConfidentialTransferFeeConfig,
},
cpi_guard, default_account_state, interest_bearing_mint, memo_transfer,
cpi_guard, default_account_state, group_pointer, interest_bearing_mint, memo_transfer,
metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions, ExtensionType,
StateWithExtensionsOwned,
},
Expand Down Expand Up @@ -178,6 +178,10 @@ pub enum ExtensionInitializationParams {
authority: Option<Pubkey>,
withdraw_withheld_authority_elgamal_pubkey: PodElGamalPubkey,
},
GroupPointer {
authority: Option<Pubkey>,
group_address: Option<Pubkey>,
},
}
impl ExtensionInitializationParams {
/// Get the extension type associated with the init params
Expand All @@ -195,6 +199,7 @@ impl ExtensionInitializationParams {
Self::ConfidentialTransferFeeConfig { .. } => {
ExtensionType::ConfidentialTransferFeeConfig
}
Self::GroupPointer { .. } => ExtensionType::GroupPointer,
}
}
/// Generate an appropriate initialization instruction for the given mint
Expand Down Expand Up @@ -286,6 +291,15 @@ impl ExtensionInitializationParams {
withdraw_withheld_authority_elgamal_pubkey,
)
}
Self::GroupPointer {
authority,
group_address,
} => group_pointer::instruction::initialize(
token_program_id,
mint,
authority,
group_address,
),
}
}
}
Expand Down Expand Up @@ -1666,6 +1680,29 @@ where
.await
}

/// Update group pointer address
pub async fn update_group_address<S: Signers>(
&self,
authority: &Pubkey,
new_group_address: Option<Pubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);

self.process_ixs(
&[group_pointer::instruction::update(
&self.program_id,
self.get_address(),
authority,
&multisig_signers,
new_group_address,
)?],
signing_keypairs,
)
.await
}

/// Update confidential transfer mint
pub async fn confidential_transfer_update_mint<S: Signers>(
&self,
Expand Down
253 changes: 253 additions & 0 deletions token/program-2022-test/tests/group_pointer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
#![cfg(feature = "test-sbf")]

mod program_test;
use {
program_test::TestContext,
solana_program_test::{processor, tokio, ProgramTest},
solana_sdk::{
instruction::InstructionError, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair,
transaction::TransactionError, transport::TransportError,
},
spl_token_2022::{
error::TokenError,
extension::{group_pointer::GroupPointer, BaseStateWithExtensions},
instruction,
processor::Processor,
},
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
std::{convert::TryInto, sync::Arc},
};

fn setup_program_test() -> ProgramTest {
let mut program_test = ProgramTest::default();
program_test.prefer_bpf(false);
program_test.add_program(
"spl_token_2022",
spl_token_2022::id(),
processor!(Processor::process),
);
program_test
}

async fn setup(mint: Keypair, group_address: &Pubkey, authority: &Pubkey) -> TestContext {
let program_test = setup_program_test();

let context = program_test.start_with_context().await;
let context = Arc::new(tokio::sync::Mutex::new(context));
let mut context = TestContext {
context,
token_context: None,
};
context
.init_token_with_mint_keypair_and_freeze_authority(
mint,
vec![ExtensionInitializationParams::GroupPointer {
authority: Some(*authority),
group_address: Some(*group_address),
}],
None,
)
.await
.unwrap();
context
}

#[tokio::test]
async fn success_init() {
let authority = Pubkey::new_unique();
let group_address = Pubkey::new_unique();
let mint_keypair = Keypair::new();
let token = setup(mint_keypair, &group_address, &authority)
.await
.token_context
.take()
.unwrap()
.token;

let state = token.get_mint_info().await.unwrap();
assert!(state.base.is_initialized);
let extension = state.get_extension::<GroupPointer>().unwrap();
assert_eq!(extension.authority, Some(authority).try_into().unwrap());
assert_eq!(
extension.group_address,
Some(group_address).try_into().unwrap()
);
}

#[tokio::test]
async fn fail_init_all_none() {
let mut program_test = ProgramTest::default();
program_test.prefer_bpf(false);
program_test.add_program(
"spl_token_2022",
spl_token_2022::id(),
processor!(Processor::process),
);
let context = program_test.start_with_context().await;
let context = Arc::new(tokio::sync::Mutex::new(context));
let mut context = TestContext {
context,
token_context: None,
};
let err = context
.init_token_with_mint_keypair_and_freeze_authority(
Keypair::new(),
vec![ExtensionInitializationParams::GroupPointer {
authority: None,
group_address: None,
}],
None,
)
.await
.unwrap_err();
assert_eq!(
err,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
1,
InstructionError::Custom(TokenError::InvalidInstruction as u32)
)
)))
);
}

#[tokio::test]
async fn set_authority() {
let authority = Keypair::new();
let group_address = Pubkey::new_unique();
let mint_keypair = Keypair::new();
let token = setup(mint_keypair, &group_address, &authority.pubkey())
.await
.token_context
.take()
.unwrap()
.token;
let new_authority = Keypair::new();

// fail, wrong signature
let wrong = Keypair::new();
let err = token
.set_authority(
token.get_address(),
&wrong.pubkey(),
Some(&new_authority.pubkey()),
instruction::AuthorityType::GroupPointer,
&[&wrong],
)
.await
.unwrap_err();
assert_eq!(
err,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenError::OwnerMismatch as u32)
)
)))
);

// success
token
.set_authority(
token.get_address(),
&authority.pubkey(),
Some(&new_authority.pubkey()),
instruction::AuthorityType::GroupPointer,
&[&authority],
)
.await
.unwrap();
let state = token.get_mint_info().await.unwrap();
let extension = state.get_extension::<GroupPointer>().unwrap();
assert_eq!(
extension.authority,
Some(new_authority.pubkey()).try_into().unwrap(),
);

// set to none
token
.set_authority(
token.get_address(),
&new_authority.pubkey(),
None,
instruction::AuthorityType::GroupPointer,
&[&new_authority],
)
.await
.unwrap();
let state = token.get_mint_info().await.unwrap();
let extension = state.get_extension::<GroupPointer>().unwrap();
assert_eq!(extension.authority, None.try_into().unwrap(),);

// fail set again
let err = token
.set_authority(
token.get_address(),
&new_authority.pubkey(),
Some(&authority.pubkey()),
instruction::AuthorityType::GroupPointer,
&[&new_authority],
)
.await
.unwrap_err();
assert_eq!(
err,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenError::AuthorityTypeNotSupported as u32)
)
)))
);
}

#[tokio::test]
async fn update_group_address() {
let authority = Keypair::new();
let group_address = Pubkey::new_unique();
let mint_keypair = Keypair::new();
let token = setup(mint_keypair, &group_address, &authority.pubkey())
.await
.token_context
.take()
.unwrap()
.token;
let new_group_address = Pubkey::new_unique();

// fail, wrong signature
let wrong = Keypair::new();
let err = token
.update_group_address(&wrong.pubkey(), Some(new_group_address), &[&wrong])
.await
.unwrap_err();
assert_eq!(
err,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenError::OwnerMismatch as u32)
)
)))
);

// success
token
.update_group_address(&authority.pubkey(), Some(new_group_address), &[&authority])
.await
.unwrap();
let state = token.get_mint_info().await.unwrap();
let extension = state.get_extension::<GroupPointer>().unwrap();
assert_eq!(
extension.group_address,
Some(new_group_address).try_into().unwrap(),
);

// set to none
token
.update_group_address(&authority.pubkey(), None, &[&authority])
.await
.unwrap();
let state = token.get_mint_info().await.unwrap();
let extension = state.get_extension::<GroupPointer>().unwrap();
assert_eq!(extension.group_address, None.try_into().unwrap(),);
}
2 changes: 1 addition & 1 deletion token/program-2022-test/tests/program_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl TestContext {
let token_unchecked = Token::new_native(Arc::clone(&client), &id(), Arc::new(payer));
self.token_context = Some(TokenContext {
decimals: native_mint::DECIMALS,
mint_authority: Keypair::new(), /*bogus*/
mint_authority: Keypair::new(), /* bogus */
token,
token_unchecked,
alice: Keypair::new(),
Expand Down
Loading

0 comments on commit c5d4211

Please sign in to comment.