Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

Commit

Permalink
token 2022: add InitializeGroup instruction from SPL Token Group in…
Browse files Browse the repository at this point in the history
…terface
  • Loading branch information
buffalojoec committed Oct 18, 2023
1 parent 82f3418 commit 5b340e5
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

281 changes: 281 additions & 0 deletions token/program-2022-test/tests/token_group_initialize_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
#![cfg(feature = "test-sbf")]

mod program_test;
use {
borsh::BorshDeserialize,
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::BaseStateWithExtensions, processor::Processor},
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
spl_token_metadata_interface::state::TokenMetadata,
std::{convert::TryInto, sync::Arc},
};

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

async fn setup(mint: Keypair, 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,
};
let metadata_address = Some(mint.pubkey());
context
.init_token_with_mint_keypair_and_freeze_authority(
mint,
vec![ExtensionInitializationParams::MetadataPointer {
authority: Some(*authority),
metadata_address,
}],
None,
)
.await
.unwrap();
context
}

#[tokio::test]
async fn success_initialize_group() {
let group = Keypair::new();
let group_mint = Keypair::new();
let group_mint_authority = Keypair::new();

let mut test_context = setup(group_mint, &group_mint_authority).await;
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
let token_context = test_context.token_context.take().unwrap();

//

// fails without more lamports for new rent-exemption
let error = token_context
.token
.token_metadata_initialize(
&update_authority,
&token_context.mint_authority.pubkey(),
token_metadata.name.clone(),
token_metadata.symbol.clone(),
token_metadata.uri.clone(),
&[&token_context.mint_authority],
)
.await
.unwrap_err();
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InsufficientFundsForRent { account_index: 2 }
)))
);

// fail wrong signer
let not_mint_authority = Keypair::new();
let error = token_context
.token
.token_metadata_initialize_with_rent_transfer(
&payer_pubkey,
&update_authority,
&not_mint_authority.pubkey(),
token_metadata.name.clone(),
token_metadata.symbol.clone(),
token_metadata.uri.clone(),
&[&not_mint_authority],
)
.await
.unwrap_err();
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
1,
InstructionError::Custom(TokenError::IncorrectMintAuthority as u32)
)
)))
);

token_context
.token
.token_metadata_initialize_with_rent_transfer(
&payer_pubkey,
&update_authority,
&token_context.mint_authority.pubkey(),
token_metadata.name.clone(),
token_metadata.symbol.clone(),
token_metadata.uri.clone(),
&[&token_context.mint_authority],
)
.await
.unwrap();

// check that the data is correct
let mint_info = token_context.token.get_mint_info().await.unwrap();
let metadata_bytes = mint_info.get_extension_bytes::<TokenMetadata>().unwrap();
let fetched_metadata = TokenMetadata::try_from_slice(metadata_bytes).unwrap();
assert_eq!(fetched_metadata, token_metadata);

// fail double-init
let error = token_context
.token
.token_metadata_initialize_with_rent_transfer(
&payer_pubkey,
&update_authority,
&token_context.mint_authority.pubkey(),
token_metadata.name.clone(),
token_metadata.symbol.clone(),
token_metadata.uri.clone(),
&[&token_context.mint_authority],
)
.await
.unwrap_err();
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenError::ExtensionAlreadyInitialized as u32)
)
)))
);
}

#[tokio::test]
async fn fail_without_metadata_pointer() {
let mut test_context = {
let mint_keypair = Keypair::new();
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_keypair, vec![], None)
.await
.unwrap();
context
};

let payer_pubkey = test_context.context.lock().await.payer.pubkey();
let token_context = test_context.token_context.take().unwrap();

let error = token_context
.token
.token_metadata_initialize_with_rent_transfer(
&payer_pubkey,
&Pubkey::new_unique(),
&token_context.mint_authority.pubkey(),
"Name".to_string(),
"Symbol".to_string(),
"URI".to_string(),
&[&token_context.mint_authority],
)
.await
.unwrap_err();
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
1,
InstructionError::Custom(TokenError::InvalidExtensionCombination as u32)
)
)))
);
}

#[tokio::test]
async fn fail_init_in_another_mint() {
let authority = Pubkey::new_unique();
let first_mint_keypair = Keypair::new();
let first_mint = first_mint_keypair.pubkey();
let mut test_context = setup(first_mint_keypair, &authority).await;
let second_mint_keypair = Keypair::new();
let second_mint = second_mint_keypair.pubkey();
test_context
.init_token_with_mint_keypair_and_freeze_authority(
second_mint_keypair,
vec![ExtensionInitializationParams::MetadataPointer {
authority: Some(authority),
metadata_address: Some(second_mint),
}],
None,
)
.await
.unwrap();

let token_context = test_context.token_context.take().unwrap();

let error = token_context
.token
.process_ixs(
&[spl_token_metadata_interface::instruction::initialize(
&spl_token_2022::id(),
&first_mint,
&Pubkey::new_unique(),
token_context.token.get_address(),
&token_context.mint_authority.pubkey(),
"Name".to_string(),
"Symbol".to_string(),
"URI".to_string(),
)],
&[&token_context.mint_authority],
)
.await
.unwrap_err();

assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(
0,
InstructionError::Custom(TokenError::MintMismatch as u32)
)
)))
);
}

#[tokio::test]
async fn fail_without_signature() {
let authority = Pubkey::new_unique();
let mint_keypair = Keypair::new();
let mut test_context = setup(mint_keypair, &authority).await;

let token_context = test_context.token_context.take().unwrap();

let mut instruction = spl_token_metadata_interface::instruction::initialize(
&spl_token_2022::id(),
token_context.token.get_address(),
&Pubkey::new_unique(),
token_context.token.get_address(),
&token_context.mint_authority.pubkey(),
"Name".to_string(),
"Symbol".to_string(),
"URI".to_string(),
);
instruction.accounts[3].is_signer = false;
let error = token_context
.token
.process_ixs(&[instruction], &[] as &[&dyn Signer; 0]) // yuck, but the compiler needs it
.await
.unwrap_err();

assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
)))
);
}
1 change: 1 addition & 0 deletions token/program-2022/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ solana-program = "1.16.16"
solana-zk-token-sdk = "1.16.16"
spl-memo = { version = "4.0.0", path = "../../memo/program", features = [ "no-entrypoint" ] }
spl-token = { version = "4.0", path = "../program", features = ["no-entrypoint"] }
spl-token-group-interface = { version = "0.1.0", path = "../../token-group/interface" }
spl-token-metadata-interface = { version = "0.2.0", path = "../../token-metadata/interface" }
spl-transfer-hook-interface = { version = "0.3.0", path = "../transfer-hook-interface" }
spl-type-length-value = { version = "0.3.0", path = "../../libraries/type-length-value" }
Expand Down
7 changes: 7 additions & 0 deletions token/program-2022/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use {
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
},
spl_token_group_interface::state::TokenGroup,
spl_pod::{
bytemuck::{pod_from_bytes, pod_from_bytes_mut, pod_get_packed_len},
primitives::PodU16,
Expand Down Expand Up @@ -68,6 +69,8 @@ pub mod non_transferable;
pub mod permanent_delegate;
/// Utility to reallocate token accounts
pub mod reallocate;
/// Token-group extension
pub mod token_group;
/// Token-metadata extension
pub mod token_metadata;
/// Transfer Fee extension
Expand Down Expand Up @@ -904,6 +907,8 @@ pub enum ExtensionType {
ConfidentialTransferFeeAmount,
/// Mint contains a pointer to another account (or the same account) that holds metadata
MetadataPointer,
/// Mint contains token group configurations
TokenGroup,
/// Mint contains token-metadata
TokenMetadata,
/// Test variable-length mint extension
Expand Down Expand Up @@ -979,6 +984,7 @@ impl ExtensionType {
pod_get_packed_len::<ConfidentialTransferFeeAmount>()
}
ExtensionType::MetadataPointer => pod_get_packed_len::<MetadataPointer>(),
ExtensionType::TokenGroup => pod_get_packed_len::<TokenGroup>(),
ExtensionType::TokenMetadata => unreachable!(),
#[cfg(test)]
ExtensionType::AccountPaddingTest => pod_get_packed_len::<AccountPaddingTest>(),
Expand Down Expand Up @@ -1039,6 +1045,7 @@ impl ExtensionType {
| ExtensionType::TransferHook
| ExtensionType::ConfidentialTransferFeeConfig
| ExtensionType::MetadataPointer
| ExtensionType::TokenGroup
| ExtensionType::TokenMetadata => AccountType::Mint,
ExtensionType::ImmutableOwner
| ExtensionType::TransferFeeAmount
Expand Down
11 changes: 11 additions & 0 deletions token/program-2022/src/extension/token_group/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use {
crate::extension::{Extension, ExtensionType},
spl_token_group_interface::state::TokenGroup,
};

/// Instruction processor for the TokenGroup extensions
pub mod processor;

impl Extension for TokenGroup {
const TYPE: ExtensionType = ExtensionType::TokenGroup;
}
Loading

0 comments on commit 5b340e5

Please sign in to comment.