Skip to content

Commit f4311a4

Browse files
mikemaccanaclaude
andcommitted
Fix metadata quasar: 234-byte account + over-fund lamports for realloc
Create mint with only base+MetadataPointer space (234 bytes) but fund lamports for full size so token-2022 can realloc during TokenMetadataInitialize. Fix TokenMetadataInitialize to use 4 accounts: [metadata=mint(writable), update_authority(readonly), mint(readonly), mint_authority(signer)]. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5dab3ae commit f4311a4

1 file changed

Lines changed: 28 additions & 32 deletions

File tree

  • tokens/token-2022/metadata/quasar/src

tokens/token-2022/metadata/quasar/src/lib.rs

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,31 @@ pub struct Initialize<'info> {
7474

7575
#[inline(always)]
7676
pub fn handle_initialize(
77-
accounts: &Initialize, name: &[u8],
77+
accounts: &Initialize,
78+
name: &[u8],
7879
symbol: &[u8],
7980
uri: &[u8],
8081
) -> Result<(), ProgramError> {
81-
// Calculate the total metadata size.
82-
// MetadataPointer (64 bytes) + TLV overhead + actual metadata
83-
// Metadata format: 4 (TLV header) + 32 (update_auth) + 32 (mint)
84-
// + 4 + name.len + 4 + symbol.len + 4 + uri.len + 4 + 0 (additional metadata)
82+
// Create account with only base + MetadataPointer TLV space initially.
83+
// TokenMetadataInitialize reallocates the account internally when called.
84+
// 165 (base) + 1 (AccountType) + 68 (MetadataPointer TLV: 2+2+64) = 234 bytes
85+
let mint_size_base: usize = 234;
86+
87+
// Compute full rent to over-fund the account so the token-2022 realloc
88+
// during TokenMetadataInitialize has sufficient lamports.
89+
// TokenMetadata TLV: 2 (type) + 2 (length) + data
90+
// data: update_auth(32) + mint(32) + name(4+len) + symbol(4+len) + uri(4+len) + additional(4)
8591
let metadata_data_len = 32 + 32 + 4 + name.len() + 4 + symbol.len() + 4 + uri.len() + 4;
86-
let total_ext_data = 4 + metadata_data_len; // TLV: 2 type + 2 length + data
87-
// 165 (base with padding) + 1 (AccountType) + 68 (MetadataPointer TLV) + metadata TLV
88-
let mint_size = 165 + 1 + 68 + total_ext_data;
89-
let lamports = Rent::get()?.try_minimum_balance(mint_size)?;
92+
let mint_size_full = mint_size_base + 4 + metadata_data_len;
93+
let lamports = Rent::get()?.try_minimum_balance(mint_size_full)?;
9094

91-
accounts.system_program
95+
accounts
96+
.system_program
9297
.create_account(
9398
accounts.payer,
9499
accounts.mint_account,
95100
lamports,
96-
mint_size as u64,
101+
mint_size_base as u64,
97102
accounts.token_program.to_account_view().address(),
98103
)
99104
.invoke()?;
@@ -118,9 +123,9 @@ pub fn handle_initialize(
118123
)
119124
.invoke()?;
120125

121-
// InitializeMint2.
126+
// InitializeMint2: opcode 20
122127
let mut mint_data = [0u8; 67];
123-
mint_data[0] = 20; // InitializeMint2
128+
mint_data[0] = 20;
124129
mint_data[1] = 2; // decimals
125130
mint_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref());
126131
mint_data[34] = 0; // no freeze authority
@@ -135,36 +140,31 @@ pub fn handle_initialize(
135140
)
136141
.invoke()?;
137142

138-
// TokenMetadataInitialize: TokenInstruction::TokenMetadataExtension = 44
139-
// Sub-instruction: Initialize = 0
140-
// Layout: [44, 0, update_authority(32), mint(32),
141-
// name_len(u32 LE), name, symbol_len(u32 LE), symbol,
142-
// uri_len(u32 LE), uri]
143+
// TokenMetadataInitialize: TokenInstruction::TokenMetadataExtension = 44, sub = 0
144+
// Data: [44, 0, update_authority(32), mint(32),
145+
// name_len(u32 LE), name, symbol_len(u32 LE), symbol, uri_len(u32 LE), uri]
146+
// Accounts: [metadata(=mint, writable), update_authority(readonly),
147+
// mint(readonly), mint_authority(signer)]
143148
const MAX_META_IX: usize = 512;
144149
let mut buf = [0u8; MAX_META_IX];
145150
let mut pos = 0usize;
146151
buf[pos] = 44;
147152
pos += 1;
148153
buf[pos] = 0;
149154
pos += 1;
150-
// update_authority
151155
buf[pos..pos + 32].copy_from_slice(accounts.payer.to_account_view().address().as_ref());
152156
pos += 32;
153-
// mint
154157
buf[pos..pos + 32]
155158
.copy_from_slice(accounts.mint_account.to_account_view().address().as_ref());
156159
pos += 32;
157-
// name
158160
buf[pos..pos + 4].copy_from_slice(&(name.len() as u32).to_le_bytes());
159161
pos += 4;
160162
buf[pos..pos + name.len()].copy_from_slice(name);
161163
pos += name.len();
162-
// symbol
163164
buf[pos..pos + 4].copy_from_slice(&(symbol.len() as u32).to_le_bytes());
164165
pos += 4;
165166
buf[pos..pos + symbol.len()].copy_from_slice(symbol);
166167
pos += symbol.len();
167-
// uri
168168
buf[pos..pos + 4].copy_from_slice(&(uri.len() as u32).to_le_bytes());
169169
pos += 4;
170170
buf[pos..pos + uri.len()].copy_from_slice(uri);
@@ -173,19 +173,15 @@ pub fn handle_initialize(
173173
quasar_lang::cpi::BufCpiCall::new(
174174
accounts.token_program.to_account_view().address(),
175175
[
176-
InstructionAccount::writable(
177-
accounts.mint_account.to_account_view().address(),
178-
),
179-
InstructionAccount::readonly_signer(
180-
accounts.payer.to_account_view().address(),
181-
),
182-
InstructionAccount::readonly_signer(
183-
accounts.payer.to_account_view().address(),
184-
),
176+
InstructionAccount::writable(accounts.mint_account.to_account_view().address()),
177+
InstructionAccount::readonly(accounts.payer.to_account_view().address()),
178+
InstructionAccount::readonly(accounts.mint_account.to_account_view().address()),
179+
InstructionAccount::readonly_signer(accounts.payer.to_account_view().address()),
185180
],
186181
[
187182
accounts.mint_account.to_account_view(),
188183
accounts.payer.to_account_view(),
184+
accounts.mint_account.to_account_view(),
189185
accounts.payer.to_account_view(),
190186
],
191187
buf,

0 commit comments

Comments
 (0)