diff --git a/Cargo.lock b/Cargo.lock index 0172de5b..ae1d7741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1554,6 +1554,15 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "hashed-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 1.0.109", +] + [[package]] name = "heck" version = "0.4.1" @@ -2094,6 +2103,7 @@ version = "0.1.0" dependencies = [ "borsh 0.10.3", "bytemuck", + "mpl-token-metadata", "mpl-utils", "num-derive 0.3.3", "num-traits", @@ -2103,6 +2113,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mpl-token-metadata" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b2de608098eb2ef2a5392069dea83084967e25a4d69d0380a6bb02454fc0fe" +dependencies = [ + "borsh 0.10.3", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "mpl-utils" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index b027c3b6..ca94b605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,3 @@ [workspace] resolver = "2" -members = [ - "clients/rust", - "programs/mpl-asset" -] +members = ["clients/rust", "programs/mpl-asset"] diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index a34dc744..1660f465 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -171,6 +171,35 @@ export class MissingCompressionProofError extends ProgramError { codeToErrorMap.set(0xb, MissingCompressionProofError); nameToErrorMap.set('MissingCompressionProof', MissingCompressionProofError); +/** CannotMigrateMasterWithSupply: Cannot migrate a master edition used for prints */ +export class CannotMigrateMasterWithSupplyError extends ProgramError { + readonly name: string = 'CannotMigrateMasterWithSupply'; + + readonly code: number = 0xc; // 12 + + constructor(program: Program, cause?: Error) { + super('Cannot migrate a master edition used for prints', program, cause); + } +} +codeToErrorMap.set(0xc, CannotMigrateMasterWithSupplyError); +nameToErrorMap.set( + 'CannotMigrateMasterWithSupply', + CannotMigrateMasterWithSupplyError +); + +/** CannotMigratePrints: Cannot migrate a print edition */ +export class CannotMigratePrintsError extends ProgramError { + readonly name: string = 'CannotMigratePrints'; + + readonly code: number = 0xd; // 13 + + constructor(program: Program, cause?: Error) { + super('Cannot migrate a print edition', program, cause); + } +} +codeToErrorMap.set(0xd, CannotMigratePrintsError); +nameToErrorMap.set('CannotMigratePrints', CannotMigratePrintsError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/migrate.ts b/clients/js/src/generated/instructions/migrate.ts index d3e2d070..44673a9f 100644 --- a/clients/js/src/generated/instructions/migrate.ts +++ b/clients/js/src/generated/instructions/migrate.ts @@ -51,7 +51,7 @@ export type MigrateInstructionAccounts = { /** Metadata (pda of ['metadata', program id, mint id]) */ metadata: PublicKey | Pda; /** Edition of token asset */ - edition?: PublicKey | Pda; + edition: PublicKey | Pda; /** Owner token record account */ ownerTokenRecord?: PublicKey | Pda; /** SPL Token Program */ diff --git a/clients/js/src/generated/types/assetSigner.ts b/clients/js/src/generated/types/assetSigner.ts new file mode 100644 index 00000000..91071730 --- /dev/null +++ b/clients/js/src/generated/types/assetSigner.ts @@ -0,0 +1,23 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; + +export type AssetSigner = {}; + +export type AssetSignerArgs = AssetSigner; + +export function getAssetSignerSerializer(): Serializer< + AssetSignerArgs, + AssetSigner +> { + return struct([], { description: 'AssetSigner' }) as Serializer< + AssetSignerArgs, + AssetSigner + >; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index f0983d70..55e142c4 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -6,6 +6,7 @@ * @see https://github.com/metaplex-foundation/kinobi */ +export * from './assetSigner'; export * from './authority'; export * from './collection'; export * from './compressionProof'; @@ -15,9 +16,11 @@ export * from './delegate'; export * from './externalPluginRecord'; export * from './extraAccounts'; export * from './key'; +export * from './legacyMetadata'; export * from './migrationLevel'; export * from './plugin'; export * from './pluginType'; export * from './registryData'; export * from './registryRecord'; export * from './royalties'; +export * from './ruleSet'; diff --git a/clients/js/src/generated/types/legacyMetadata.ts b/clients/js/src/generated/types/legacyMetadata.ts new file mode 100644 index 00000000..154c142e --- /dev/null +++ b/clients/js/src/generated/types/legacyMetadata.ts @@ -0,0 +1,27 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; + +export type LegacyMetadata = { mint: PublicKey }; + +export type LegacyMetadataArgs = LegacyMetadata; + +export function getLegacyMetadataSerializer(): Serializer< + LegacyMetadataArgs, + LegacyMetadata +> { + return struct([['mint', publicKeySerializer()]], { + description: 'LegacyMetadata', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index e10faa68..aea54d7a 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -16,23 +16,33 @@ import { unit, } from '@metaplex-foundation/umi/serializers'; import { + AssetSigner, + AssetSignerArgs, Delegate, DelegateArgs, + LegacyMetadata, + LegacyMetadataArgs, Royalties, RoyaltiesArgs, + getAssetSignerSerializer, getDelegateSerializer, + getLegacyMetadataSerializer, getRoyaltiesSerializer, } from '.'; export type Plugin = | { __kind: 'Reserved' } | { __kind: 'Royalties'; fields: [Royalties] } - | { __kind: 'Delegate'; fields: [Delegate] }; + | { __kind: 'Delegate'; fields: [Delegate] } + | { __kind: 'LegacyMetadata'; fields: [LegacyMetadata] } + | { __kind: 'AssetSigner'; fields: [AssetSigner] }; export type PluginArgs = | { __kind: 'Reserved' } | { __kind: 'Royalties'; fields: [RoyaltiesArgs] } - | { __kind: 'Delegate'; fields: [DelegateArgs] }; + | { __kind: 'Delegate'; fields: [DelegateArgs] } + | { __kind: 'LegacyMetadata'; fields: [LegacyMetadataArgs] } + | { __kind: 'AssetSigner'; fields: [AssetSignerArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( @@ -50,6 +60,18 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getDelegateSerializer()])], ]), ], + [ + 'LegacyMetadata', + struct>([ + ['fields', tuple([getLegacyMetadataSerializer()])], + ]), + ], + [ + 'AssetSigner', + struct>([ + ['fields', tuple([getAssetSignerSerializer()])], + ]), + ], ], { description: 'Plugin' } ) as Serializer; @@ -67,6 +89,14 @@ export function plugin( kind: 'Delegate', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; +export function plugin( + kind: 'LegacyMetadata', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function plugin( + kind: 'AssetSigner', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; export function plugin( kind: K, data?: any diff --git a/clients/js/src/generated/types/pluginType.ts b/clients/js/src/generated/types/pluginType.ts index 9948bd21..c8baa0ca 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -12,6 +12,8 @@ export enum PluginType { Reserved, Royalties, Delegate, + LegacyMetadata, + AssetSigner, } export type PluginTypeArgs = PluginType; diff --git a/clients/js/src/generated/types/royalties.ts b/clients/js/src/generated/types/royalties.ts index 77382e10..8698fd55 100644 --- a/clients/js/src/generated/types/royalties.ts +++ b/clients/js/src/generated/types/royalties.ts @@ -6,34 +6,39 @@ * @see https://github.com/metaplex-foundation/kinobi */ -import { PublicKey } from '@metaplex-foundation/umi'; import { Serializer, array, - publicKey as publicKeySerializer, struct, u16, } from '@metaplex-foundation/umi/serializers'; -import { Creator, CreatorArgs, getCreatorSerializer } from '.'; +import { + Creator, + CreatorArgs, + RuleSet, + RuleSetArgs, + getCreatorSerializer, + getRuleSetSerializer, +} from '.'; export type Royalties = { - creators: Array; - authRules: PublicKey; sellerFeeBasisPoints: number; + creators: Array; + ruleSet: RuleSet; }; export type RoyaltiesArgs = { - creators: Array; - authRules: PublicKey; sellerFeeBasisPoints: number; + creators: Array; + ruleSet: RuleSetArgs; }; export function getRoyaltiesSerializer(): Serializer { return struct( [ - ['creators', array(getCreatorSerializer())], - ['authRules', publicKeySerializer()], ['sellerFeeBasisPoints', u16()], + ['creators', array(getCreatorSerializer())], + ['ruleSet', getRuleSetSerializer()], ], { description: 'Royalties' } ) as Serializer; diff --git a/clients/js/src/generated/types/ruleSet.ts b/clients/js/src/generated/types/ruleSet.ts new file mode 100644 index 00000000..33c76ced --- /dev/null +++ b/clients/js/src/generated/types/ruleSet.ts @@ -0,0 +1,69 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + array, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; + +export type RuleSet = + | { __kind: 'ProgramAllowList'; fields: [Array] } + | { __kind: 'ProgramDenyList'; fields: [Array] }; + +export type RuleSetArgs = RuleSet; + +export function getRuleSetSerializer(): Serializer { + return dataEnum( + [ + [ + 'ProgramAllowList', + struct>([ + ['fields', tuple([array(publicKeySerializer())])], + ]), + ], + [ + 'ProgramDenyList', + struct>([ + ['fields', tuple([array(publicKeySerializer())])], + ]), + ], + ], + { description: 'RuleSet' } + ) as Serializer; +} + +// Data Enum Helpers. +export function ruleSet( + kind: 'ProgramAllowList', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function ruleSet( + kind: 'ProgramDenyList', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function ruleSet( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isRuleSet( + kind: K, + value: RuleSet +): value is RuleSet & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index d1019602..97d4749d 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -46,6 +46,12 @@ pub enum MplAssetError { /// 11 (0xB) - Missing compression proof #[error("Missing compression proof")] MissingCompressionProof, + /// 12 (0xC) - Cannot migrate a master edition used for prints + #[error("Cannot migrate a master edition used for prints")] + CannotMigrateMasterWithSupply, + /// 13 (0xD) - Cannot migrate a print edition + #[error("Cannot migrate a print edition")] + CannotMigratePrints, } impl solana_program::program_error::PrintProgramError for MplAssetError { diff --git a/clients/rust/src/generated/instructions/migrate.rs b/clients/rust/src/generated/instructions/migrate.rs index ac3d3b84..c3fedab0 100644 --- a/clients/rust/src/generated/instructions/migrate.rs +++ b/clients/rust/src/generated/instructions/migrate.rs @@ -27,7 +27,7 @@ pub struct Migrate { /// Metadata (pda of ['metadata', program id, mint id]) pub metadata: solana_program::pubkey::Pubkey, /// Edition of token asset - pub edition: Option, + pub edition: solana_program::pubkey::Pubkey, /// Owner token record account pub owner_token_record: Option, /// SPL Token Program @@ -95,16 +95,10 @@ impl Migrate { self.metadata, false, )); - if let Some(edition) = self.edition { - accounts.push(solana_program::instruction::AccountMeta::new( - edition, false, - )); - } else { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - crate::MPL_ASSET_ID, - false, - )); - } + accounts.push(solana_program::instruction::AccountMeta::new( + self.edition, + false, + )); if let Some(owner_token_record) = self.owner_token_record { accounts.push(solana_program::instruction::AccountMeta::new( owner_token_record, @@ -263,11 +257,10 @@ impl MigrateBuilder { self.metadata = Some(metadata); self } - /// `[optional account]` /// Edition of token asset #[inline(always)] - pub fn edition(&mut self, edition: Option) -> &mut Self { - self.edition = edition; + pub fn edition(&mut self, edition: solana_program::pubkey::Pubkey) -> &mut Self { + self.edition = Some(edition); self } /// `[optional account]` @@ -375,7 +368,7 @@ impl MigrateBuilder { token: self.token.expect("token is not set"), mint: self.mint.expect("mint is not set"), metadata: self.metadata.expect("metadata is not set"), - edition: self.edition, + edition: self.edition.expect("edition is not set"), owner_token_record: self.owner_token_record, spl_token_program: self.spl_token_program.unwrap_or(solana_program::pubkey!( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" @@ -416,7 +409,7 @@ pub struct MigrateCpiAccounts<'a, 'b> { /// Metadata (pda of ['metadata', program id, mint id]) pub metadata: &'b solana_program::account_info::AccountInfo<'a>, /// Edition of token asset - pub edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + pub edition: &'b solana_program::account_info::AccountInfo<'a>, /// Owner token record account pub owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// SPL Token Program @@ -452,7 +445,7 @@ pub struct MigrateCpi<'a, 'b> { /// Metadata (pda of ['metadata', program id, mint id]) pub metadata: &'b solana_program::account_info::AccountInfo<'a>, /// Edition of token asset - pub edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + pub edition: &'b solana_program::account_info::AccountInfo<'a>, /// Owner token record account pub owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// SPL Token Program @@ -572,17 +565,10 @@ impl<'a, 'b> MigrateCpi<'a, 'b> { *self.metadata.key, false, )); - if let Some(edition) = self.edition { - accounts.push(solana_program::instruction::AccountMeta::new( - *edition.key, - false, - )); - } else { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - crate::MPL_ASSET_ID, - false, - )); - } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.edition.key, + false, + )); if let Some(owner_token_record) = self.owner_token_record { accounts.push(solana_program::instruction::AccountMeta::new( *owner_token_record.key, @@ -668,9 +654,7 @@ impl<'a, 'b> MigrateCpi<'a, 'b> { account_infos.push(self.token.clone()); account_infos.push(self.mint.clone()); account_infos.push(self.metadata.clone()); - if let Some(edition) = self.edition { - account_infos.push(edition.clone()); - } + account_infos.push(self.edition.clone()); if let Some(owner_token_record) = self.owner_token_record { account_infos.push(owner_token_record.clone()); } @@ -784,14 +768,13 @@ impl<'a, 'b> MigrateCpiBuilder<'a, 'b> { self.instruction.metadata = Some(metadata); self } - /// `[optional account]` /// Edition of token asset #[inline(always)] pub fn edition( &mut self, - edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + edition: &'b solana_program::account_info::AccountInfo<'a>, ) -> &mut Self { - self.instruction.edition = edition; + self.instruction.edition = Some(edition); self } /// `[optional account]` @@ -940,7 +923,7 @@ impl<'a, 'b> MigrateCpiBuilder<'a, 'b> { metadata: self.instruction.metadata.expect("metadata is not set"), - edition: self.instruction.edition, + edition: self.instruction.edition.expect("edition is not set"), owner_token_record: self.instruction.owner_token_record, diff --git a/clients/rust/src/generated/types/asset_signer.rs b/clients/rust/src/generated/types/asset_signer.rs new file mode 100644 index 00000000..6e0f43d5 --- /dev/null +++ b/clients/rust/src/generated/types/asset_signer.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AssetSigner {} diff --git a/clients/rust/src/generated/types/legacy_metadata.rs b/clients/rust/src/generated/types/legacy_metadata.rs new file mode 100644 index 00000000..62f38977 --- /dev/null +++ b/clients/rust/src/generated/types/legacy_metadata.rs @@ -0,0 +1,20 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct LegacyMetadata { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub mint: Pubkey, +} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index d6d3d813..ee58c6cd 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +pub(crate) mod asset_signer; pub(crate) mod authority; pub(crate) mod collection; pub(crate) mod compression_proof; @@ -14,13 +15,16 @@ pub(crate) mod delegate; pub(crate) mod external_plugin_record; pub(crate) mod extra_accounts; pub(crate) mod key; +pub(crate) mod legacy_metadata; pub(crate) mod migration_level; pub(crate) mod plugin; pub(crate) mod plugin_type; pub(crate) mod registry_data; pub(crate) mod registry_record; pub(crate) mod royalties; +pub(crate) mod rule_set; +pub use self::asset_signer::*; pub use self::authority::*; pub use self::collection::*; pub use self::compression_proof::*; @@ -30,9 +34,11 @@ pub use self::delegate::*; pub use self::external_plugin_record::*; pub use self::extra_accounts::*; pub use self::key::*; +pub use self::legacy_metadata::*; pub use self::migration_level::*; pub use self::plugin::*; pub use self::plugin_type::*; pub use self::registry_data::*; pub use self::registry_record::*; pub use self::royalties::*; +pub use self::rule_set::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 17f2d2e7..328a0c08 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -5,7 +5,9 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::AssetSigner; use crate::generated::types::Delegate; +use crate::generated::types::LegacyMetadata; use crate::generated::types::Royalties; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -16,4 +18,6 @@ pub enum Plugin { Reserved, Royalties(Royalties), Delegate(Delegate), + LegacyMetadata(LegacyMetadata), + AssetSigner(AssetSigner), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index 5a846ec4..6decb21c 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -14,4 +14,6 @@ pub enum PluginType { Reserved, Royalties, Delegate, + LegacyMetadata, + AssetSigner, } diff --git a/clients/rust/src/generated/types/royalties.rs b/clients/rust/src/generated/types/royalties.rs index 55a3009b..b975007e 100644 --- a/clients/rust/src/generated/types/royalties.rs +++ b/clients/rust/src/generated/types/royalties.rs @@ -6,18 +6,14 @@ //! use crate::generated::types::Creator; +use crate::generated::types::RuleSet; use borsh::BorshDeserialize; use borsh::BorshSerialize; -use solana_program::pubkey::Pubkey; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Royalties { - pub creators: Vec, - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] - pub auth_rules: Pubkey, pub seller_fee_basis_points: u16, + pub creators: Vec, + pub rule_set: RuleSet, } diff --git a/clients/rust/src/generated/types/rule_set.rs b/clients/rust/src/generated/types/rule_set.rs new file mode 100644 index 00000000..d6acd201 --- /dev/null +++ b/clients/rust/src/generated/types/rule_set.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RuleSet { + ProgramAllowList(Vec), + ProgramDenyList(Vec), +} diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index 96e2ae5a..ffd7e39a 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -144,7 +144,6 @@ "name": "edition", "isMut": true, "isSigner": false, - "isOptional": true, "docs": [ "Edition of token asset" ] @@ -862,6 +861,13 @@ ] } }, + { + "name": "AssetSigner", + "type": { + "kind": "struct", + "fields": [] + } + }, { "name": "Collection", "type": { @@ -890,6 +896,18 @@ ] } }, + { + "name": "LegacyMetadata", + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "type": "publicKey" + } + ] + } + }, { "name": "Creator", "type": { @@ -911,6 +929,10 @@ "type": { "kind": "struct", "fields": [ + { + "name": "sellerFeeBasisPoints", + "type": "u16" + }, { "name": "creators", "type": { @@ -920,12 +942,10 @@ } }, { - "name": "authRules", - "type": "publicKey" - }, - { - "name": "sellerFeeBasisPoints", - "type": "u16" + "name": "ruleSet", + "type": { + "defined": "RuleSet" + } } ] } @@ -1099,6 +1119,22 @@ "defined": "Delegate" } ] + }, + { + "name": "LegacyMetadata", + "fields": [ + { + "defined": "LegacyMetadata" + } + ] + }, + { + "name": "AssetSigner", + "fields": [ + { + "defined": "AssetSigner" + } + ] } ] } @@ -1116,6 +1152,36 @@ }, { "name": "Delegate" + }, + { + "name": "LegacyMetadata" + }, + { + "name": "AssetSigner" + } + ] + } + }, + { + "name": "RuleSet", + "type": { + "kind": "enum", + "variants": [ + { + "name": "ProgramAllowList", + "fields": [ + { + "vec": "publicKey" + } + ] + }, + { + "name": "ProgramDenyList", + "fields": [ + { + "vec": "publicKey" + } + ] } ] } @@ -1318,6 +1384,16 @@ "code": 11, "name": "MissingCompressionProof", "msg": "Missing compression proof" + }, + { + "code": 12, + "name": "CannotMigrateMasterWithSupply", + "msg": "Cannot migrate a master edition used for prints" + }, + { + "code": 13, + "name": "CannotMigratePrints", + "msg": "Cannot migrate a print edition" } ], "metadata": { diff --git a/programs/mpl-asset/Cargo.toml b/programs/mpl-asset/Cargo.toml index 156bfc77..6c320c1a 100644 --- a/programs/mpl-asset/Cargo.toml +++ b/programs/mpl-asset/Cargo.toml @@ -19,3 +19,4 @@ thiserror = "^1.0" bytemuck = "1.14.1" mpl-utils = "0.3.3" spl-noop = { version = "0.2.0", features = ["cpi"] } +mpl-token-metadata = "4.1.1" diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index 33fa710b..68f2fbaf 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -55,6 +55,14 @@ pub enum MplAssetError { /// 11 - Missing compression proof #[error("Missing compression proof")] MissingCompressionProof, + + /// 12 - Cannot migrate a master edition used for prints + #[error("Cannot migrate a master edition used for prints")] + CannotMigrateMasterWithSupply, + + /// 13 - Cannot migrate a print edition + #[error("Cannot migrate a print edition")] + CannotMigratePrints, } impl PrintProgramError for MplAssetError { diff --git a/programs/mpl-asset/src/instruction.rs b/programs/mpl-asset/src/instruction.rs index 38e49791..fbc3a04a 100644 --- a/programs/mpl-asset/src/instruction.rs +++ b/programs/mpl-asset/src/instruction.rs @@ -21,6 +21,7 @@ pub enum MplAssetInstruction { Create(CreateArgs), //TODO: Implement this instruction + // keith WIP /// Migrate an mpl-token-metadata asset to an mpl-asset. #[account(0, writable, signer, name="asset_address", desc = "The address of the new asset")] #[account(1, optional, signer, name="owner", desc = "The authority of the new asset")] @@ -30,7 +31,7 @@ pub enum MplAssetInstruction { #[account(4, writable, name="token", desc="Token account")] #[account(5, writable, name="mint", desc="Mint of token asset")] #[account(6, writable, name="metadata", desc="Metadata (pda of ['metadata', program id, mint id])")] - #[account(7, optional, writable, name="edition", desc="Edition of token asset")] + #[account(7, writable, name="edition", desc="Edition of token asset")] #[account(8, optional, writable, name="owner_token_record", desc="Owner token record account")] #[account(9, name="spl_token_program", desc="SPL Token Program")] #[account(10, name="spl_ata_program", desc="SPL Associated Token Account program")] @@ -40,7 +41,6 @@ pub enum MplAssetInstruction { #[account(14, optional, name="authorization_rules", desc="Token Authorization Rules account")] Migrate(MigrateArgs), - //TODO: Implement this instruction /// Delegate an mpl-asset. #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] @@ -80,14 +80,12 @@ pub enum MplAssetInstruction { #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] Update(UpdateArgs), - //TODO: Implement this instruction /// Freeze an mpl-asset. #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, signer, name="delegate", desc = "The delegate of the asset")] #[account(2, optional, name="log_wrapper", desc = "The SPL Noop Program")] Freeze(FreezeArgs), - //TODO: Implement this instruction /// Thaw an mpl-asset. #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, signer, name="delegate", desc = "The delegate of the asset")] diff --git a/programs/mpl-asset/src/plugins/asset_signer.rs b/programs/mpl-asset/src/plugins/asset_signer.rs new file mode 100644 index 00000000..fca2783b --- /dev/null +++ b/programs/mpl-asset/src/plugins/asset_signer.rs @@ -0,0 +1,17 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::state::DataBlob; + +#[repr(C)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct AssetSigner {} + +impl DataBlob for AssetSigner { + fn get_initial_size() -> usize { + 0 + } + + fn get_size(&self) -> usize { + 0 + } +} diff --git a/programs/mpl-asset/src/plugins/legacy_metadata.rs b/programs/mpl-asset/src/plugins/legacy_metadata.rs new file mode 100644 index 00000000..20538f8d --- /dev/null +++ b/programs/mpl-asset/src/plugins/legacy_metadata.rs @@ -0,0 +1,20 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::state::DataBlob; + +#[repr(C)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct LegacyMetadata { + pub mint: Pubkey, // 32 +} + +impl DataBlob for LegacyMetadata { + fn get_initial_size() -> usize { + 32 + } + + fn get_size(&self) -> usize { + 32 + } +} diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index 83caa55d..e80f5657 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -1,10 +1,14 @@ +mod asset_signer; mod collection; mod delegate; +mod legacy_metadata; mod royalties; mod utils; +pub use asset_signer::*; pub use collection::*; pub use delegate::*; +pub use legacy_metadata::*; pub use royalties::*; use shank::ShankAccount; @@ -26,6 +30,8 @@ pub enum Plugin { Reserved, Royalties(Royalties), Delegate(Delegate), + LegacyMetadata(LegacyMetadata), + AssetSigner(AssetSigner), } #[repr(u16)] @@ -34,6 +40,20 @@ pub enum PluginType { Reserved, Royalties, Delegate, + LegacyMetadata, + AssetSigner, +} + +impl From<&Plugin> for PluginType { + fn from(plugin: &Plugin) -> Self { + match plugin { + Plugin::Reserved => PluginType::Reserved, + Plugin::Royalties(_) => PluginType::Royalties, + Plugin::Delegate(_) => PluginType::Delegate, + Plugin::LegacyMetadata(_) => PluginType::LegacyMetadata, + Plugin::AssetSigner(_) => PluginType::AssetSigner, + } + } } impl Plugin { diff --git a/programs/mpl-asset/src/plugins/royalties.rs b/programs/mpl-asset/src/plugins/royalties.rs index 10e5d90b..5ae48a52 100644 --- a/programs/mpl-asset/src/plugins/royalties.rs +++ b/programs/mpl-asset/src/plugins/royalties.rs @@ -7,9 +7,15 @@ pub struct Creator { verified: bool, } +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub enum RuleSet { + ProgramAllowList(Vec), + ProgramDenyList(Vec), +} + #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct Royalties { - creators: Vec, - auth_rules: Pubkey, seller_fee_basis_points: u16, + creators: Vec, + rule_set: RuleSet, } diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 685a0b7e..84834fd4 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -2,7 +2,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::resize_or_reallocate_account_raw; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - program_memory::sol_memcpy, }; use crate::{ @@ -133,11 +132,7 @@ pub fn add_plugin_or_authority<'a>( let mut header = PluginHeader::load(account, asset.get_size())?; let mut plugin_registry = PluginRegistry::load(account, header.plugin_registry_offset)?; - let plugin_type = match plugin { - Plugin::Reserved => return Err(MplAssetError::InvalidPlugin.into()), - Plugin::Royalties(_) => PluginType::Royalties, - Plugin::Delegate(_) => PluginType::Delegate, - }; + let plugin_type = plugin.into(); let plugin_data = plugin.try_to_vec()?; let plugin_size = plugin_data.len(); let authority_bytes = authority.try_to_vec()?; diff --git a/programs/mpl-asset/src/processor/migrate.rs b/programs/mpl-asset/src/processor/migrate.rs index 9fc24dc1..3c656a9c 100644 --- a/programs/mpl-asset/src/processor/migrate.rs +++ b/programs/mpl-asset/src/processor/migrate.rs @@ -1,14 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_token_metadata::accounts::{MasterEdition, Metadata}; use mpl_utils::assert_signer; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplAssetError, - instruction::accounts::CreateAccounts, - state::{Asset, Compressible, DataState, HashedAsset, Key, MigrationLevel}, + instruction::accounts::MigrateAccounts, + state::{DataState, MigrationLevel}, }; #[repr(C)] @@ -19,5 +17,39 @@ pub struct MigrateArgs { } pub(crate) fn migrate<'a>(accounts: &'a [AccountInfo<'a>], args: MigrateArgs) -> ProgramResult { + let ctx = MigrateAccounts::context(accounts)?; + + // Assert that the owner is the one performing the migration. + assert_signer(ctx.accounts.payer)?; + let authority = if let Some(owner) = ctx.accounts.owner { + assert_signer(owner)?; + owner + } else { + ctx.accounts.payer + }; + + let metadata = Metadata::safe_deserialize(&ctx.accounts.metadata.data.borrow())?; + + match metadata.collection_details { + // If this is a collection NFT then we need to initialize migration for the whole collection. + Some(_) => {} + // Otherwise, we need to migrate the NFT + None => { + // Assert that the NFT is not a print edition. + if ctx.accounts.edition.data.borrow()[1] + == mpl_token_metadata::types::Key::EditionV1 as u8 + { + return Err(MplAssetError::CannotMigratePrints.into()); + } + + // Assert that the NFT is not a master edition with a nonzero supply. + let master_edition = + MasterEdition::safe_deserialize(&ctx.accounts.edition.data.borrow())?; + if master_edition.max_supply != Some(0) { + return Err(MplAssetError::CannotMigrateMasterWithSupply.into()); + } + } + } + Ok(()) }