-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
token-group: create collections example
- Loading branch information
1 parent
3185a0e
commit 4515e06
Showing
12 changed files
with
1,314 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[package] | ||
name = "spl-token-group-example" | ||
version = "0.1.0" | ||
description = "Solana Program Library Token Group Example" | ||
authors = ["Solana Labs Maintainers <[email protected]>"] | ||
repository = "https://github.com/solana-labs/solana-program-library" | ||
license = "Apache-2.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
solana-program = "1.16.16" | ||
spl-pod = { version = "0.1.0", path = "../../libraries/pod" } | ||
spl-token-2022 = { version = "0.9.0", path = "../../token/program-2022" } | ||
spl-token-group-interface = { version = "0.1.0", path = "../interface" } | ||
spl-type-length-value = { version = "0.3.0", path = "../../libraries/type-length-value" } | ||
|
||
[dev-dependencies] | ||
solana-program-test = "1.16.16" | ||
solana-sdk = "1.16.16" | ||
spl-discriminator = { version = "0.1.0", path = "../../libraries/discriminator" } | ||
spl-token-client = { version = "0.7", path = "../../token/client" } | ||
spl-token-metadata-interface = { version = "0.2", path = "../../token-metadata/interface" } | ||
|
||
[lib] | ||
crate-type = ["cdylib", "lib"] | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
//! Program entrypoint | ||
use { | ||
crate::processor, | ||
solana_program::{ | ||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, | ||
program_error::PrintProgramError, pubkey::Pubkey, | ||
}, | ||
spl_token_group_interface::error::TokenGroupError, | ||
}; | ||
|
||
entrypoint!(process_instruction); | ||
fn process_instruction( | ||
program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
instruction_data: &[u8], | ||
) -> ProgramResult { | ||
if let Err(error) = processor::process(program_id, accounts, instruction_data) { | ||
error.print::<TokenGroupError>(); | ||
return Err(error); | ||
} | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//! Crate defining an example program for creating SPL token collections | ||
//! using the SPL Token Group interface. | ||
#![deny(missing_docs)] | ||
#![cfg_attr(not(test), forbid(unsafe_code))] | ||
|
||
pub mod processor; | ||
|
||
#[cfg(not(feature = "no-entrypoint"))] | ||
mod entrypoint; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
//! Program state processor | ||
use { | ||
solana_program::{ | ||
account_info::{next_account_info, AccountInfo}, | ||
entrypoint::ProgramResult, | ||
msg, | ||
program_error::ProgramError, | ||
program_option::COption, | ||
pubkey::Pubkey, | ||
}, | ||
spl_pod::optional_keys::OptionalNonZeroPubkey, | ||
spl_token_2022::{extension::StateWithExtensions, state::Mint}, | ||
spl_token_group_interface::{ | ||
error::TokenGroupError, | ||
instruction::{ | ||
InitializeGroup, TokenGroupInterfaceInstruction, UpdateGroupAuthority, | ||
UpdateGroupMaxSize, | ||
}, | ||
state::{Group, Member}, | ||
}, | ||
spl_type_length_value::state::TlvStateMut, | ||
}; | ||
|
||
fn check_update_authority( | ||
update_authority_info: &AccountInfo, | ||
expected_update_authority: &OptionalNonZeroPubkey, | ||
) -> Result<(), ProgramError> { | ||
if !update_authority_info.is_signer { | ||
return Err(ProgramError::MissingRequiredSignature); | ||
} | ||
let update_authority = Option::<Pubkey>::from(*expected_update_authority) | ||
.ok_or(TokenGroupError::ImmutableGroup)?; | ||
if update_authority != *update_authority_info.key { | ||
return Err(TokenGroupError::IncorrectAuthority.into()); | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Processes a [InitializeGroup](enum.GroupInterfaceInstruction.html) | ||
/// instruction for a `Collection` | ||
pub fn process_initialize_collection( | ||
_program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
data: InitializeGroup, | ||
) -> ProgramResult { | ||
// Assumes one has already created a mint for the collection. | ||
let account_info_iter = &mut accounts.iter(); | ||
|
||
// Accounts expected by this instruction: | ||
// | ||
// 0. `[w]` Collection (Group) | ||
// 1. `[]` Mint | ||
// 2. `[s]` Mint authority | ||
let collection_info = next_account_info(account_info_iter)?; | ||
let mint_info = next_account_info(account_info_iter)?; | ||
let mint_authority_info = next_account_info(account_info_iter)?; | ||
|
||
{ | ||
// IMPORTANT: this example program is designed to work with any | ||
// program that implements the SPL token interface, so there is no | ||
// ownership check on the mint account. | ||
let mint_data = mint_info.try_borrow_data()?; | ||
let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?; | ||
|
||
if !mint_authority_info.is_signer { | ||
return Err(ProgramError::MissingRequiredSignature); | ||
} | ||
if mint.base.mint_authority.as_ref() != COption::Some(mint_authority_info.key) { | ||
return Err(TokenGroupError::IncorrectAuthority.into()); | ||
} | ||
} | ||
|
||
// Allocate a TLV entry for the space and write it in | ||
let mut buffer = collection_info.try_borrow_mut_data()?; | ||
let mut state = TlvStateMut::unpack(&mut buffer)?; | ||
let (collection, _) = state.init_value::<Group>(false)?; | ||
*collection = Group::new(data.update_authority, data.max_size.into()); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Processes an | ||
/// [UpdateGroupMaxSize](enum.GroupInterfaceInstruction.html) | ||
/// instruction for a `Collection` | ||
pub fn process_update_collection_max_size( | ||
_program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
data: UpdateGroupMaxSize, | ||
) -> ProgramResult { | ||
let account_info_iter = &mut accounts.iter(); | ||
|
||
// Accounts expected by this instruction: | ||
// | ||
// 0. `[w]` Collection (Group) | ||
// 1. `[s]` Update authority | ||
let collection_info = next_account_info(account_info_iter)?; | ||
let update_authority_info = next_account_info(account_info_iter)?; | ||
|
||
let mut buffer = collection_info.try_borrow_mut_data()?; | ||
let mut state = TlvStateMut::unpack(&mut buffer)?; | ||
let collection = state.get_first_value_mut::<Group>()?; | ||
|
||
check_update_authority(update_authority_info, &collection.update_authority)?; | ||
|
||
// Update the max size (zero-copy) | ||
collection.update_max_size(data.max_size.into())?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Processes a | ||
/// [UpdateGroupAuthority](enum.GroupInterfaceInstruction.html) | ||
/// instruction for a `Collection` | ||
pub fn process_update_collection_authority( | ||
_program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
data: UpdateGroupAuthority, | ||
) -> ProgramResult { | ||
let account_info_iter = &mut accounts.iter(); | ||
|
||
// Accounts expected by this instruction: | ||
// | ||
// 0. `[w]` Collection (Group) | ||
// 1. `[s]` Current update authority | ||
let collection_info = next_account_info(account_info_iter)?; | ||
let update_authority_info = next_account_info(account_info_iter)?; | ||
|
||
let mut buffer = collection_info.try_borrow_mut_data()?; | ||
let mut state = TlvStateMut::unpack(&mut buffer)?; | ||
let mut collection = state.get_first_value_mut::<Group>()?; | ||
|
||
check_update_authority(update_authority_info, &collection.update_authority)?; | ||
|
||
// Update the authority (zero-copy) | ||
collection.update_authority = data.new_authority; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Processes a [InitializeMember](enum.GroupInterfaceInstruction.html) | ||
/// instruction for a `Collection` | ||
pub fn process_initialize_collection_member( | ||
_program_id: &Pubkey, | ||
accounts: &[AccountInfo], | ||
) -> ProgramResult { | ||
// For this group, we are going to assume the collection has been | ||
// initialized, and we're also assuming a mint _and_ metadata have been | ||
// created for the member. | ||
// Collection memebers in this example can have their own separate | ||
// metadata that differs from the metadata of the collection. | ||
let account_info_iter = &mut accounts.iter(); | ||
|
||
// Accounts expected by this instruction: | ||
// | ||
// 0. `[w]` Collection Member (Member) | ||
// 1. `[]` Collection Member (Member) Mint | ||
// 2. `[s]` Collection Member (Member) Mint authority | ||
// 3. `[w]` Collection (Group) | ||
// 4. `[s]` Collection (Group) update authority | ||
let member_info = next_account_info(account_info_iter)?; | ||
let member_mint_info = next_account_info(account_info_iter)?; | ||
let member_mint_authority_info = next_account_info(account_info_iter)?; | ||
let collection_info = next_account_info(account_info_iter)?; | ||
let collection_update_authority_info = next_account_info(account_info_iter)?; | ||
|
||
// Mint checks on the member | ||
{ | ||
// IMPORTANT: this example program is designed to work with any | ||
// program that implements the SPL token interface, so there is no | ||
// ownership check on the mint account. | ||
let member_mint_data = member_mint_info.try_borrow_data()?; | ||
let member_mint = StateWithExtensions::<Mint>::unpack(&member_mint_data)?; | ||
|
||
if !member_mint_authority_info.is_signer { | ||
return Err(ProgramError::MissingRequiredSignature); | ||
} | ||
if member_mint.base.mint_authority.as_ref() != COption::Some(member_mint_authority_info.key) | ||
{ | ||
return Err(TokenGroupError::IncorrectAuthority.into()); | ||
} | ||
} | ||
|
||
// Increment the size of the collection | ||
let mut buffer = collection_info.try_borrow_mut_data()?; | ||
let mut state = TlvStateMut::unpack(&mut buffer)?; | ||
let collection = state.get_first_value_mut::<Group>()?; | ||
|
||
check_update_authority( | ||
collection_update_authority_info, | ||
&collection.update_authority, | ||
)?; | ||
let member_number = collection.increment_size()?; | ||
|
||
// Allocate a TLV entry for the space and write it in | ||
let mut buffer = member_info.try_borrow_mut_data()?; | ||
let mut state = TlvStateMut::unpack(&mut buffer)?; | ||
let (member, _) = state.init_value::<Member>(false)?; | ||
*member = Member::new(*collection_info.key, member_number.into()); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Processes an `SplTokenGroupInstruction` | ||
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { | ||
let instruction = TokenGroupInterfaceInstruction::unpack(input)?; | ||
match instruction { | ||
TokenGroupInterfaceInstruction::InitializeGroup(data) => { | ||
msg!("Instruction: InitializeCollection"); | ||
process_initialize_collection(program_id, accounts, data) | ||
} | ||
TokenGroupInterfaceInstruction::UpdateGroupMaxSize(data) => { | ||
msg!("Instruction: UpdateCollectionMaxSize"); | ||
process_update_collection_max_size(program_id, accounts, data) | ||
} | ||
TokenGroupInterfaceInstruction::UpdateGroupAuthority(data) => { | ||
msg!("Instruction: UpdateCollectionAuthority"); | ||
process_update_collection_authority(program_id, accounts, data) | ||
} | ||
TokenGroupInterfaceInstruction::InitializeMember(_) => { | ||
msg!("Instruction: InitializeCollectionMember"); | ||
process_initialize_collection_member(program_id, accounts) | ||
} | ||
} | ||
} |
Oops, something went wrong.