Skip to content

Commit a94bcb0

Browse files
authored
Sync metadata to token-2022 pt. 1 (#221)
* Sync token-2022 metadata * Review updates
1 parent 29b44fe commit a94bcb0

20 files changed

+924
-194
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ level = "warn"
1919
check-cfg = [
2020
'cfg(target_os, values("solana"))',
2121
'cfg(feature, values("frozen-abi", "no-entrypoint"))',
22+
'cfg(feature, values("custom-heap", "custom-panic"))',
2223
]
2324

2425
[workspace.package]
@@ -77,6 +78,7 @@ spl-token-2022 = { version = "9.0.0", features = ["no-entrypoint"] }
7778
spl-token-metadata-interface = "0.7.0"
7879
spl-token-wrap = { path = "program", features = ["no-entrypoint"] }
7980
spl-transfer-hook-interface = "0.10.0"
81+
spl-type-length-value = "0.8.0"
8082
tempfile = "3.20.0"
8183
test-case = "3.3.1"
8284
test-transfer-hook = { path = "program/tests/programs/test-transfer-hook", features = ["no-entrypoint"] }

clients/cli/src/create_mint.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ use {
1616
solana_transaction::Transaction,
1717
spl_token::solana_program::program_pack::Pack,
1818
spl_token_wrap::{
19-
get_wrapped_mint_address, get_wrapped_mint_authority, get_wrapped_mint_backpointer_address,
20-
id,
19+
get_wrapped_mint_address, get_wrapped_mint_backpointer_address, id,
2120
instruction::create_mint,
2221
mint_customizer::{
2322
default_token_2022::DefaultToken2022Customizer, interface::MintCustomizer,
@@ -123,7 +122,7 @@ pub async fn command_create_mint(config: &Config, args: CreateMintArgs) -> Comma
123122
};
124123

125124
let mint_size = if args.wrapped_token_program == spl_token_2022::id() {
126-
DefaultToken2022Customizer::get_token_2022_total_space()?
125+
DefaultToken2022Customizer::get_token_2022_mint_space()?
127126
} else {
128127
spl_token::state::Mint::LEN
129128
};
@@ -177,13 +176,11 @@ pub async fn command_create_mint(config: &Config, args: CreateMintArgs) -> Comma
177176
}
178177

179178
// Add the create_mint instruction
180-
let wrapped_mint_authority_address = get_wrapped_mint_authority(&wrapped_mint_address);
181179
instructions.push(create_mint(
182180
&id(),
183181
&wrapped_mint_address,
184182
&wrapped_backpointer_address,
185183
&args.unwrapped_mint,
186-
&wrapped_mint_authority_address,
187184
&args.wrapped_token_program,
188185
args.idempotent,
189186
));

clients/cli/tests/test_create_mint.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use {
1111
},
1212
pod::PodMint,
1313
},
14-
spl_token_metadata_interface::state::TokenMetadata,
1514
spl_token_wrap::{
1615
self, get_wrapped_mint_address, get_wrapped_mint_authority,
1716
get_wrapped_mint_backpointer_address, state::Backpointer,
@@ -65,7 +64,7 @@ async fn test_create_mint() {
6564
assert_eq!(backpointer.unwrapped_mint, unwrapped_mint);
6665

6766
// Verify extension state
68-
assert_eq!(wrapped_mint_state.get_extension_types().unwrap().len(), 3);
67+
assert_eq!(wrapped_mint_state.get_extension_types().unwrap().len(), 2);
6968

7069
assert!(wrapped_mint_state
7170
.get_extension::<ConfidentialTransferMint>()
@@ -84,14 +83,4 @@ async fn test_create_mint() {
8483
Option::<Pubkey>::from(pointer_ext.metadata_address).unwrap(),
8584
wrapped_mint_address
8685
);
87-
88-
// Verify TokenMetadata content
89-
let metadata_ext = wrapped_mint_state
90-
.get_variable_len_extension::<TokenMetadata>()
91-
.unwrap();
92-
assert_eq!(
93-
Option::<Pubkey>::from(metadata_ext.update_authority).unwrap(),
94-
expected_mint_authority
95-
);
96-
assert_eq!(metadata_ext.mint, wrapped_mint_address);
9786
}

program/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ spl-token-2022 = { workspace = true }
4141
mollusk-svm = { workspace = true }
4242
mollusk-svm-programs-token = { workspace = true }
4343
solana-account = { workspace = true }
44+
spl-type-length-value = { workspace = true }
4445
spl-tlv-account-resolution = { workspace = true }
4546
test-case = { workspace = true }
4647
test-transfer-hook = { workspace = true }

program/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub enum TokenWrapError {
4242
/// The escrow account is in a good state and cannot be recreated
4343
#[error("The escrow account is in a good state and cannot be recreated")]
4444
EscrowInGoodState,
45+
/// Unwrapped mint does not have the `TokenMetadata` extension
46+
#[error("Unwrapped mint does not have the TokenMetadata extension")]
47+
UnwrappedMintHasNoMetadata,
4548
}
4649

4750
impl From<TokenWrapError> for ProgramError {
@@ -73,6 +76,7 @@ impl ToStr for TokenWrapError {
7376
TokenWrapError::InvalidBackpointerOwner => "Error: InvalidBackpointerOwner",
7477
TokenWrapError::EscrowMismatch => "Error: EscrowMismatch",
7578
TokenWrapError::EscrowInGoodState => "Error: EscrowInGoodState",
79+
TokenWrapError::UnwrappedMintHasNoMetadata => "Error: UnwrappedMintHasNoMetadata",
7680
}
7781
}
7882
}

program/src/instruction.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ pub enum TokenWrapInstruction {
108108
/// 4. `[]` Wrapped mint authority (PDA)
109109
/// 5. `[]` Token-2022 program
110110
CloseStuckEscrow,
111+
112+
/// This instruction copies the metadata fields from an unwrapped mint to
113+
/// its wrapped mint `TokenMetadata` extension.
114+
///
115+
/// Supports (unwrapped to wrapped):
116+
/// - Token-2022 to Token-2022
117+
/// - SPL-token to Token-2022 (still `TODO`)
118+
///
119+
/// If the `TokenMetadata` extension on the wrapped mint if not present, it
120+
/// will initialize it. The client is responsible for funding the wrapped
121+
/// mint account with enough lamports to cover the rent for the
122+
/// additional space required by the `TokenMetadata` extension and/or
123+
/// metadata sync.
124+
///
125+
/// Accounts expected by this instruction:
126+
///
127+
/// 0. `[w]` Wrapped mint
128+
/// 1. `[]` Wrapped mint authority (PDA)
129+
/// 2. `[]` Unwrapped mint
130+
/// 3. `[]` Token-2022 program
131+
SyncMetadataToToken2022,
111132
}
112133

113134
impl TokenWrapInstruction {
@@ -132,6 +153,9 @@ impl TokenWrapInstruction {
132153
TokenWrapInstruction::CloseStuckEscrow => {
133154
buf.push(3);
134155
}
156+
TokenWrapInstruction::SyncMetadataToToken2022 => {
157+
buf.push(4);
158+
}
135159
}
136160
buf
137161
}
@@ -157,6 +181,7 @@ impl TokenWrapInstruction {
157181
Ok(TokenWrapInstruction::Unwrap { amount })
158182
}
159183
Some((&3, [])) => Ok(TokenWrapInstruction::CloseStuckEscrow),
184+
Some((&4, [])) => Ok(TokenWrapInstruction::SyncMetadataToToken2022),
160185
_ => Err(ProgramError::InvalidInstructionData),
161186
}
162187
}
@@ -168,15 +193,13 @@ pub fn create_mint(
168193
wrapped_mint_address: &Pubkey,
169194
wrapped_backpointer_address: &Pubkey,
170195
unwrapped_mint_address: &Pubkey,
171-
wrapped_mint_authority_address: &Pubkey,
172196
wrapped_token_program_id: &Pubkey,
173197
idempotent: bool,
174198
) -> Instruction {
175199
let accounts = vec![
176200
AccountMeta::new(*wrapped_mint_address, false),
177201
AccountMeta::new(*wrapped_backpointer_address, false),
178202
AccountMeta::new_readonly(*unwrapped_mint_address, false),
179-
AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
180203
AccountMeta::new_readonly(solana_system_interface::program::id(), false),
181204
AccountMeta::new_readonly(*wrapped_token_program_id, false),
182205
];
@@ -280,3 +303,20 @@ pub fn close_stuck_escrow(
280303
let data = TokenWrapInstruction::CloseStuckEscrow.pack();
281304
Instruction::new_with_bytes(*program_id, &data, accounts)
282305
}
306+
307+
/// Creates `SyncMetadataToToken2022` instruction.
308+
pub fn sync_metadata_to_token_2022(
309+
program_id: &Pubkey,
310+
wrapped_mint: &Pubkey,
311+
wrapped_mint_authority: &Pubkey,
312+
unwrapped_mint: &Pubkey,
313+
) -> Instruction {
314+
let accounts = vec![
315+
AccountMeta::new(*wrapped_mint, false),
316+
AccountMeta::new_readonly(*wrapped_mint_authority, false),
317+
AccountMeta::new_readonly(*unwrapped_mint, false),
318+
AccountMeta::new_readonly(spl_token_2022::id(), false),
319+
];
320+
let data = TokenWrapInstruction::SyncMetadataToToken2022.pack();
321+
Instruction::new_with_bytes(*program_id, &data, accounts)
322+
}

program/src/mint_customizer/default_token_2022.rs

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use {
22
crate::{get_wrapped_mint_authority, mint_customizer::interface::MintCustomizer},
33
solana_account_info::AccountInfo,
4-
solana_cpi::{invoke, invoke_signed},
4+
solana_cpi::invoke,
55
solana_program_error::{ProgramError, ProgramResult},
66
solana_pubkey::Pubkey,
77
spl_token_2022::{
@@ -14,17 +14,14 @@ use {
1414
pod::PodMint,
1515
state::Mint,
1616
},
17-
spl_token_metadata_interface::{
18-
instruction::initialize as initialize_token_metadata, state::TokenMetadata,
19-
},
2017
};
2118

22-
/// This implementation adds the `ConfidentialTransferMint` & `TokenMetadata`
19+
/// This implementation adds the `ConfidentialTransferMint` & `MetadataPointer`
2320
/// extensions by default.
2421
pub struct DefaultToken2022Customizer;
2522

2623
impl MintCustomizer for DefaultToken2022Customizer {
27-
fn get_token_2022_mint_initialization_space() -> Result<usize, ProgramError> {
24+
fn get_token_2022_mint_space() -> Result<usize, ProgramError> {
2825
// Calculate space for all extensions that are initialized *before* the base
2926
// mint. The TokenMetadata extension is initialized *after* and its
3027
// `initialize` instruction handles its own reallocation.
@@ -34,15 +31,7 @@ impl MintCustomizer for DefaultToken2022Customizer {
3431
])
3532
}
3633

37-
fn get_token_2022_total_space() -> Result<usize, ProgramError> {
38-
let base_size = Self::get_token_2022_mint_initialization_space()?;
39-
let metadata_size = TokenMetadata::default().tlv_size_of()?;
40-
base_size
41-
.checked_add(metadata_size)
42-
.ok_or(ProgramError::ArithmeticOverflow)
43-
}
44-
45-
fn pre_initialize_extensions(
34+
fn initialize_extensions(
4635
wrapped_mint_account: &AccountInfo,
4736
wrapped_token_program_account: &AccountInfo,
4837
) -> ProgramResult {
@@ -73,42 +62,6 @@ impl MintCustomizer for DefaultToken2022Customizer {
7362
Ok(())
7463
}
7564

76-
fn post_initialize_extensions<'a>(
77-
wrapped_mint_account: &AccountInfo<'a>,
78-
wrapped_token_program_account: &AccountInfo,
79-
wrapped_mint_authority_account: &AccountInfo<'a>,
80-
mint_authority_signer_seeds: &[&[u8]],
81-
) -> ProgramResult {
82-
// Initialize metadata ext (must be done after mint initialization)
83-
let wrapped_mint_authority = get_wrapped_mint_authority(wrapped_mint_account.key);
84-
85-
let cpi_accounts = [
86-
wrapped_mint_account.clone(),
87-
wrapped_mint_authority_account.clone(),
88-
wrapped_mint_account.clone(),
89-
wrapped_mint_authority_account.clone(),
90-
];
91-
92-
invoke_signed(
93-
&initialize_token_metadata(
94-
wrapped_token_program_account.key,
95-
wrapped_mint_account.key,
96-
&wrapped_mint_authority,
97-
wrapped_mint_account.key,
98-
&wrapped_mint_authority,
99-
// Initialized as empty, but separate instructions are available
100-
// to update these fields
101-
"".to_string(),
102-
"".to_string(),
103-
"".to_string(),
104-
),
105-
&cpi_accounts,
106-
&[mint_authority_signer_seeds],
107-
)?;
108-
109-
Ok(())
110-
}
111-
11265
fn get_freeze_auth_and_decimals(
11366
unwrapped_mint_account: &AccountInfo,
11467
) -> Result<(Option<Pubkey>, u8), ProgramError> {

program/src/mint_customizer/interface.rs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,17 @@ use {
88
pub trait MintCustomizer {
99
/// Calculates the space required for a new spl-token-2022 mint
1010
/// account, including any custom extensions
11-
fn get_token_2022_mint_initialization_space() -> Result<usize, ProgramError>;
12-
13-
/// Calculates the total space required for a new spl-token-2022
14-
/// mint, after `post_initialize_extensions()` is called. This is useful in
15-
/// calculating rent requirements if something like `TokenMetadata` does a
16-
/// realloc after the mint is created. If not implemented, defaults to
17-
/// `get_token_2022_mint_initialization_space()` result.
18-
fn get_token_2022_total_space() -> Result<usize, ProgramError> {
19-
Self::get_token_2022_mint_initialization_space()
20-
}
11+
fn get_token_2022_mint_space() -> Result<usize, ProgramError>;
2112

2213
/// Customizes extensions for the wrapped mint *before* the base mint is
2314
/// initialized. This is for extensions that must be initialized on an
2415
/// uninitialized mint account, like `ConfidentialTransferMint`.
25-
fn pre_initialize_extensions(
26-
wrapped_mint_account: &AccountInfo,
27-
wrapped_token_program_account: &AccountInfo,
28-
) -> ProgramResult;
29-
30-
/// Customizes extensions for the wrapped mint *after* the base mint is
31-
/// initialized. This is for extensions that require the mint to be
32-
/// initialized, like `TokenMetadata`.
33-
fn post_initialize_extensions<'a>(
34-
wrapped_mint_account: &AccountInfo<'a>,
35-
wrapped_token_program_account: &AccountInfo,
36-
wrapped_mint_authority_account: &AccountInfo<'a>,
37-
mint_authority_signer_seeds: &[&[u8]],
38-
) -> ProgramResult;
16+
fn initialize_extensions(
17+
_wrapped_mint_account: &AccountInfo,
18+
_wrapped_token_program_account: &AccountInfo,
19+
) -> ProgramResult {
20+
Ok(())
21+
}
3922

4023
/// Customize the freeze authority and decimals for the wrapped mint
4124
fn get_freeze_auth_and_decimals(

program/src/mint_customizer/no_extensions.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use {
22
crate::mint_customizer::interface::MintCustomizer,
33
solana_account_info::AccountInfo,
4-
solana_program_error::{ProgramError, ProgramResult},
4+
solana_program_error::ProgramError,
55
solana_pubkey::Pubkey,
66
spl_token_2022::{
77
extension::{ExtensionType, PodStateWithExtensions},
@@ -14,27 +14,11 @@ use {
1414
pub struct NoExtensionCustomizer;
1515

1616
impl MintCustomizer for NoExtensionCustomizer {
17-
fn get_token_2022_mint_initialization_space() -> Result<usize, ProgramError> {
17+
fn get_token_2022_mint_space() -> Result<usize, ProgramError> {
1818
let extensions = vec![];
1919
ExtensionType::try_calculate_account_len::<Mint>(&extensions)
2020
}
2121

22-
fn pre_initialize_extensions(
23-
_wrapped_mint_account: &AccountInfo,
24-
_wrapped_token_program_account: &AccountInfo,
25-
) -> ProgramResult {
26-
Ok(())
27-
}
28-
29-
fn post_initialize_extensions<'a>(
30-
_wrapped_mint_account: &AccountInfo<'a>,
31-
_wrapped_token_program_account: &AccountInfo,
32-
_wrapped_mint_authority_account: &AccountInfo<'a>,
33-
_mint_authority_signer_seeds: &[&[u8]],
34-
) -> ProgramResult {
35-
Ok(())
36-
}
37-
3822
fn get_freeze_auth_and_decimals(
3923
unwrapped_mint_account: &AccountInfo,
4024
) -> Result<(Option<Pubkey>, u8), ProgramError> {

0 commit comments

Comments
 (0)