1use borsh::{maybestd::io::Error as BorshError, BorshDeserialize, BorshSerialize};
2use mpl_utils::{create_or_allocate_account_raw, token::get_mint_authority};
3use solana_program::{
4 account_info::AccountInfo, entrypoint::ProgramResult, program_option::COption, pubkey::Pubkey,
5};
6
7use super::{compression::is_decompression, *};
8use crate::{
9 assertions::{
10 assert_mint_authority_matches_mint, assert_owned_by,
11 collection::assert_collection_update_is_valid, metadata::assert_data_valid,
12 uses::assert_valid_use,
13 },
14 state::{
15 Collection, CollectionDetails, Data, DataV2, Key, Metadata, ProgrammableConfig,
16 TokenStandard, Uses, EDITION, MAX_METADATA_LEN, PREFIX,
17 },
18};
19
20pub const SEED_AUTHORITY: Pubkey = Pubkey::new_from_array([
29 0x92, 0x17, 0x2c, 0xc4, 0x72, 0x5d, 0xc0, 0x41, 0xf9, 0xdd, 0x8c, 0x51, 0x52, 0x60, 0x04, 0x26,
30 0x00, 0x93, 0xa3, 0x0b, 0x02, 0x73, 0xdc, 0xfa, 0x74, 0x92, 0x17, 0xfc, 0x94, 0xa2, 0x40, 0x49,
31]);
32
33pub struct CreateMetadataAccountsLogicArgs<'a> {
37 pub metadata_account_info: &'a AccountInfo<'a>,
38 pub mint_info: &'a AccountInfo<'a>,
39 pub mint_authority_info: &'a AccountInfo<'a>,
40 pub payer_account_info: &'a AccountInfo<'a>,
41 pub update_authority_info: &'a AccountInfo<'a>,
42 pub system_account_info: &'a AccountInfo<'a>,
43}
44
45pub fn process_create_metadata_accounts_logic(
47 program_id: &Pubkey,
48 accounts: CreateMetadataAccountsLogicArgs,
49 data: DataV2,
50 allow_direct_creator_writes: bool,
51 mut is_mutable: bool,
52 is_edition: bool,
53 add_token_standard: bool,
54 collection_details: Option<CollectionDetails>,
55) -> ProgramResult {
56 let CreateMetadataAccountsLogicArgs {
57 metadata_account_info,
58 mint_info,
59 mint_authority_info,
60 payer_account_info,
61 update_authority_info,
62 system_account_info,
63 } = accounts;
64
65 let mut update_authority_key = *update_authority_info.key;
66 let existing_mint_authority = get_mint_authority(mint_info)?;
67
68 assert_mint_authority_matches_mint(&existing_mint_authority, mint_authority_info).or_else(
72 |e| {
73 if mint_authority_info.key == &SEED_AUTHORITY && mint_authority_info.is_signer {
75 if let COption::Some(auth) = existing_mint_authority {
77 update_authority_key = auth;
78 is_mutable = true;
79 }
80 Ok(())
81 } else {
82 Err(e)
83 }
84 },
85 )?;
86 assert_owned_by(mint_info, &spl_token::ID)?;
87
88 let metadata_seeds = &[
89 PREFIX.as_bytes(),
90 program_id.as_ref(),
91 mint_info.key.as_ref(),
92 ];
93 let (metadata_key, metadata_bump_seed) =
94 Pubkey::find_program_address(metadata_seeds, program_id);
95 let metadata_authority_signer_seeds = &[
96 PREFIX.as_bytes(),
97 program_id.as_ref(),
98 mint_info.key.as_ref(),
99 &[metadata_bump_seed],
100 ];
101
102 if metadata_account_info.key != &metadata_key {
103 return Err(MetadataError::InvalidMetadataKey.into());
104 }
105
106 create_or_allocate_account_raw(
107 *program_id,
108 metadata_account_info,
109 system_account_info,
110 payer_account_info,
111 MAX_METADATA_LEN,
112 metadata_authority_signer_seeds,
113 )?;
114
115 let mut metadata = Metadata::from_account_info(metadata_account_info)?;
116 let compatible_data = data.to_v1();
117
118 let is_decompression = is_decompression(mint_info, mint_authority_info);
121 let allow_direct_creator_writes = allow_direct_creator_writes || is_decompression;
122
123 assert_data_valid(
124 &compatible_data,
125 &update_authority_key,
126 &metadata,
127 allow_direct_creator_writes,
128 update_authority_info.is_signer,
129 )?;
130
131 let mint_decimals = get_mint_decimals(mint_info)?;
132
133 metadata.mint = *mint_info.key;
134 metadata.key = Key::MetadataV1;
135 metadata.data = data.to_v1();
136 metadata.is_mutable = is_mutable;
137 metadata.update_authority = update_authority_key;
138
139 assert_valid_use(&data.uses, &None)?;
140 metadata.uses = data.uses;
141
142 let allow_direct_collection_verified_writes = is_edition || is_decompression;
144 assert_collection_update_is_valid(
145 allow_direct_collection_verified_writes,
146 &None,
147 &data.collection,
148 )?;
149 metadata.collection = data.collection;
150
151 if let Some(details) = collection_details {
154 match details {
155 CollectionDetails::V1 { size: _size } => {
156 metadata.collection_details = Some(CollectionDetails::V1 { size: 0 });
157 }
158 }
159 } else {
160 metadata.collection_details = None;
161 }
162
163 if add_token_standard {
164 let token_standard = if is_edition {
165 TokenStandard::NonFungibleEdition
166 } else if mint_decimals == 0 {
167 TokenStandard::FungibleAsset
168 } else {
169 TokenStandard::Fungible
170 };
171 metadata.token_standard = Some(token_standard);
172 } else {
173 metadata.token_standard = None;
174 }
175 puff_out_data_fields(&mut metadata);
176
177 let edition_seeds = &[
178 PREFIX.as_bytes(),
179 program_id.as_ref(),
180 metadata.mint.as_ref(),
181 EDITION.as_bytes(),
182 ];
183 let (_, edition_bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
184 metadata.edition_nonce = Some(edition_bump_seed);
185 metadata.save(&mut metadata_account_info.data.borrow_mut())?;
187
188 Ok(())
189}
190
191pub fn meta_deser_unchecked(buf: &mut &[u8]) -> Result<Metadata, BorshError> {
199 let key: Key = BorshDeserialize::deserialize(buf)?;
201 let update_authority: Pubkey = BorshDeserialize::deserialize(buf)?;
202 let mint: Pubkey = BorshDeserialize::deserialize(buf)?;
203 let data: Data = BorshDeserialize::deserialize(buf)?;
204 let primary_sale_happened: bool = BorshDeserialize::deserialize(buf)?;
205 let is_mutable: bool = BorshDeserialize::deserialize(buf)?;
206 let edition_nonce: Option<u8> = BorshDeserialize::deserialize(buf)?;
207
208 let token_standard_res: Result<Option<TokenStandard>, BorshError> =
210 BorshDeserialize::deserialize(buf);
211 let collection_res: Result<Option<Collection>, BorshError> = BorshDeserialize::deserialize(buf);
212 let uses_res: Result<Option<Uses>, BorshError> = BorshDeserialize::deserialize(buf);
213
214 let collection_details_res: Result<Option<CollectionDetails>, BorshError> =
216 BorshDeserialize::deserialize(buf);
217
218 let programmable_config_res: Result<Option<ProgrammableConfig>, BorshError> =
220 BorshDeserialize::deserialize(buf);
221
222 let (token_standard, collection, uses) = match (token_standard_res, collection_res, uses_res) {
226 (Ok(token_standard_res), Ok(collection_res), Ok(uses_res)) => {
227 (token_standard_res, collection_res, uses_res)
228 }
229 _ => (None, None, None),
230 };
231
232 let collection_details = match collection_details_res {
234 Ok(details) => details,
235 Err(_) => None,
236 };
237
238 let programmable_config = programmable_config_res.unwrap_or(None);
240
241 let metadata = Metadata {
242 key,
243 update_authority,
244 mint,
245 data,
246 primary_sale_happened,
247 is_mutable,
248 edition_nonce,
249 token_standard,
250 collection,
251 uses,
252 collection_details,
253 programmable_config,
254 };
255
256 Ok(metadata)
257}
258
259pub fn clean_write_metadata(
260 metadata: &mut Metadata,
261 metadata_account_info: &AccountInfo,
262) -> ProgramResult {
263 let mut metadata_account_info_data = metadata_account_info.try_borrow_mut_data()?;
265 metadata_account_info_data[0..].fill(0);
266
267 metadata.serialize(&mut *metadata_account_info_data)?;
268
269 Ok(())
270}
271
272#[cfg(test)]
273pub mod tests {
274 use solana_program::pubkey;
275
276 use super::*;
277 pub use crate::{state::Creator, utils::puff_out_data_fields};
278
279 pub fn pesky_data() -> &'static [u8] {
282 &[
283 4, 12, 25, 250, 103, 242, 3, 129, 143, 173, 110, 204, 157, 11, 1, 247, 211, 138, 199,
284 219, 79, 142, 183, 195, 96, 206, 63, 208, 102, 152, 127, 62, 43, 181, 253, 142, 126,
285 95, 96, 46, 202, 26, 76, 133, 228, 219, 191, 64, 186, 139, 115, 88, 216, 76, 125, 144,
286 12, 216, 198, 54, 196, 128, 102, 191, 96, 32, 0, 0, 0, 80, 101, 115, 107, 121, 32, 80,
287 101, 110, 103, 117, 105, 110, 115, 32, 35, 56, 48, 54, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0,
288 0, 0, 0, 10, 0, 0, 0, 78, 79, 79, 84, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0, 104, 116, 116,
289 112, 115, 58, 47, 47, 97, 114, 119, 101, 97, 118, 101, 46, 110, 101, 116, 47, 72, 122,
290 79, 110, 102, 78, 77, 87, 81, 66, 72, 84, 57, 118, 48, 68, 87, 56, 69, 114, 57, 89, 70,
291 119, 100, 105, 71, 74, 88, 52, 45, 117, 75, 57, 82, 83, 89, 65, 82, 56, 102, 120, 69,
292 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
293 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
294 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
295 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
296 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 1, 1, 3, 0, 0, 0,
297 135, 35, 134, 27, 83, 153, 173, 73, 166, 213, 73, 13, 254, 1, 156, 113, 34, 24, 205,
298 42, 233, 242, 137, 173, 173, 195, 214, 108, 110, 42, 89, 229, 1, 0, 12, 25, 250, 103,
299 242, 3, 129, 143, 173, 110, 204, 157, 11, 1, 247, 211, 138, 199, 219, 79, 142, 183,
300 195, 96, 206, 63, 208, 102, 152, 127, 62, 43, 1, 40, 12, 63, 245, 233, 144, 127, 205,
301 69, 77, 225, 56, 60, 107, 184, 84, 240, 194, 136, 55, 121, 217, 128, 246, 223, 140, 64,
302 40, 122, 145, 17, 203, 60, 0, 60, 1, 1, 1, 255, 149, 248, 123, 137, 230, 77, 203, 8,
303 124, 145, 63, 132, 220, 224, 64, 60, 253, 17, 33, 18, 81, 80, 186, 15, 248, 247, 249,
304 243, 1, 20, 26, 244, 47, 94, 35, 232, 64, 68, 124, 40, 100, 36, 93, 190, 82, 38, 36,
305 149, 248, 56, 72, 95, 68, 50, 157, 1, 155, 95, 113, 49, 247, 176, 1, 20, 1, 1, 1, 255,
306 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
307 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
308 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
309 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
310 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
311 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
312 0, 0, 0, 0, 0,
313 ]
314 }
315
316 pub fn expected_pesky_metadata() -> Metadata {
317 let creators = vec![
318 Creator {
319 address: pubkey!("A6XTVFiwGVsG6b6LsvQTGnV5LH3Pfa3qW3TGz8RjToLp"),
320 verified: true,
321 share: 0,
322 },
323 Creator {
324 address: pubkey!("pEsKYABNARLiDFYrjbjHDieD5h6gHrvYf9Vru62NX9k"),
325 verified: true,
326 share: 40,
327 },
328 Creator {
329 address: pubkey!("ppTeamTpw1cbC8ybJpppbnoL7xXD9froJNFb5uvoPvb"),
330 verified: false,
331 share: 60,
332 },
333 ];
334
335 let data = Data {
336 name: "Pesky Penguins #8060".to_string(),
337 symbol: "NOOT".to_string(),
338 uri: "https://arweave.net/HzOnfNMWQBHT9v0DW8Er9YFwdiGJX4-uK9RSYAR8fxE".to_string(),
339 seller_fee_basis_points: 500,
340 creators: Some(creators),
341 };
342
343 let mut metadata = Metadata {
344 key: Key::MetadataV1,
345 update_authority: pubkey!("pEsKYABNARLiDFYrjbjHDieD5h6gHrvYf9Vru62NX9k"),
346 mint: pubkey!("DFR3KjTso6PFCyUtq48a2aPZQpMMoaFgtbdxtaLxF2TR"),
347 data,
348 primary_sale_happened: true,
349 is_mutable: true,
350 edition_nonce: Some(255),
351 token_standard: None,
352 collection: None,
353 uses: None,
354 collection_details: None,
355 programmable_config: None,
356 };
357
358 puff_out_data_fields(&mut metadata);
359
360 metadata
361 }
362
363 #[test]
364 fn deserialize_corrupted_metadata() {
365 let mut buf = pesky_data();
366 let metadata = meta_deser_unchecked(&mut buf).unwrap();
367 let expected_metadata = expected_pesky_metadata();
368
369 assert_eq!(metadata, expected_metadata);
370 }
371}