diff --git a/clients/js/src/generated/errors/mplCore.ts b/clients/js/src/generated/errors/mplCore.ts index 6befb4d4..eb898f40 100644 --- a/clients/js/src/generated/errors/mplCore.ts +++ b/clients/js/src/generated/errors/mplCore.ts @@ -304,6 +304,32 @@ export class MissingNewOwnerError extends ProgramError { codeToErrorMap.set(0x15, MissingNewOwnerError); nameToErrorMap.set('MissingNewOwner', MissingNewOwnerError); +/** MissingSystemProgram: Missing system program */ +export class MissingSystemProgramError extends ProgramError { + override readonly name: string = 'MissingSystemProgram'; + + readonly code: number = 0x16; // 22 + + constructor(program: Program, cause?: Error) { + super('Missing system program', program, cause); + } +} +codeToErrorMap.set(0x16, MissingSystemProgramError); +nameToErrorMap.set('MissingSystemProgram', MissingSystemProgramError); + +/** NotImplemented: Not implemented */ +export class NotImplementedError extends ProgramError { + override readonly name: string = 'NotImplemented'; + + readonly code: number = 0x17; // 23 + + constructor(program: Program, cause?: Error) { + super('Not implemented', program, cause); + } +} +codeToErrorMap.set(0x17, NotImplementedError); +nameToErrorMap.set('NotImplemented', NotImplementedError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/burn.ts b/clients/js/src/generated/instructions/burn.ts index fb613af3..07fb61c3 100644 --- a/clients/js/src/generated/instructions/burn.ts +++ b/clients/js/src/generated/instructions/burn.ts @@ -44,6 +44,8 @@ export type BurnInstructionAccounts = { authority?: Signer; /** The account paying for the storage fees */ payer?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; /** The SPL Noop Program */ logWrapper?: PublicKey | Pda; }; @@ -110,9 +112,14 @@ export function burn( isWritable: true as boolean, value: input.payer ?? null, }, - logWrapper: { + systemProgram: { index: 4, isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false as boolean, value: input.logWrapper ?? null, }, } satisfies ResolvedAccountsWithIndices; diff --git a/clients/js/src/generated/instructions/compress.ts b/clients/js/src/generated/instructions/compress.ts index df7e8c1c..eb447f2d 100644 --- a/clients/js/src/generated/instructions/compress.ts +++ b/clients/js/src/generated/instructions/compress.ts @@ -33,7 +33,7 @@ export type CompressInstructionAccounts = { /** The collection to which the asset belongs */ collection?: PublicKey | Pda; /** The owner or delegate of the asset */ - owner: Signer; + authority?: Signer; /** The account receiving the storage fees */ payer?: Signer; /** The system program */ @@ -65,7 +65,7 @@ export function getCompressInstructionDataSerializer(): Serializer< // Instruction. export function compress( - context: Pick, + context: Pick, input: CompressInstructionAccounts ): TransactionBuilder { // Program ID. @@ -86,10 +86,10 @@ export function compress( isWritable: false as boolean, value: input.collection ?? null, }, - owner: { + authority: { index: 2, isWritable: false as boolean, - value: input.owner ?? null, + value: input.authority ?? null, }, payer: { index: 3, @@ -109,6 +109,9 @@ export function compress( } satisfies ResolvedAccountsWithIndices; // Default values. + if (!resolvedAccounts.authority.value) { + resolvedAccounts.authority.value = context.identity; + } if (!resolvedAccounts.systemProgram.value) { resolvedAccounts.systemProgram.value = context.programs.getPublicKey( 'splSystem', diff --git a/clients/js/src/generated/instructions/decompress.ts b/clients/js/src/generated/instructions/decompress.ts index b21078de..a44941d8 100644 --- a/clients/js/src/generated/instructions/decompress.ts +++ b/clients/js/src/generated/instructions/decompress.ts @@ -38,7 +38,7 @@ export type DecompressInstructionAccounts = { /** The collection to which the asset belongs */ collection?: PublicKey | Pda; /** The owner or delegate of the asset */ - owner: Signer; + authority?: Signer; /** The account paying for the storage fees */ payer?: Signer; /** The system program */ @@ -82,7 +82,7 @@ export type DecompressInstructionArgs = DecompressInstructionDataArgs; // Instruction. export function decompress( - context: Pick, + context: Pick, input: DecompressInstructionAccounts & DecompressInstructionArgs ): TransactionBuilder { // Program ID. @@ -103,10 +103,10 @@ export function decompress( isWritable: false as boolean, value: input.collection ?? null, }, - owner: { + authority: { index: 2, isWritable: false as boolean, - value: input.owner ?? null, + value: input.authority ?? null, }, payer: { index: 3, @@ -129,6 +129,9 @@ export function decompress( const resolvedArgs: DecompressInstructionArgs = { ...input }; // Default values. + if (!resolvedAccounts.authority.value) { + resolvedAccounts.authority.value = context.identity; + } if (!resolvedAccounts.systemProgram.value) { resolvedAccounts.systemProgram.value = context.programs.getPublicKey( 'splSystem', diff --git a/clients/js/src/generated/instructions/transfer.ts b/clients/js/src/generated/instructions/transfer.ts index aaffa9ca..1e97a069 100644 --- a/clients/js/src/generated/instructions/transfer.ts +++ b/clients/js/src/generated/instructions/transfer.ts @@ -46,6 +46,8 @@ export type TransferInstructionAccounts = { payer?: Signer; /** The new owner to which to transfer the asset */ newOwner: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; /** The SPL Noop Program */ logWrapper?: PublicKey | Pda; }; @@ -121,9 +123,14 @@ export function transfer( isWritable: false as boolean, value: input.newOwner ?? null, }, - logWrapper: { + systemProgram: { index: 5, isWritable: false as boolean, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 6, + isWritable: false as boolean, value: input.logWrapper ?? null, }, } satisfies ResolvedAccountsWithIndices; diff --git a/clients/js/test/compress.test.ts b/clients/js/test/compress.test.ts index 4a3b3c51..23d6380c 100644 --- a/clients/js/test/compress.test.ts +++ b/clients/js/test/compress.test.ts @@ -1,4 +1,4 @@ -import { generateSigner } from '@metaplex-foundation/umi'; +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; import test from 'ava'; import { Asset, @@ -44,7 +44,8 @@ test('it can compress an asset without any plugins as the owner', async (t) => { // And when we compress the asset. await compress(umi, { asset: assetAddress.publicKey, - owner: umi.identity, + authority: umi.identity, + logWrapper: publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'), }).sendAndConfirm(umi); // console.log('Compress signature: ', bs58.encode(tx.signature)); @@ -92,7 +93,7 @@ test('it cannot compress an asset if not the owner', async (t) => { const result = compress(umi, { asset: assetAddress.publicKey, - owner: attacker, + authority: attacker, }).sendAndConfirm(umi); await t.throwsAsync(result, { name: 'InvalidAuthority' }); diff --git a/clients/js/test/create.test.ts b/clients/js/test/create.test.ts index 5b601ee7..69e45f02 100644 --- a/clients/js/test/create.test.ts +++ b/clients/js/test/create.test.ts @@ -9,8 +9,6 @@ import { create, fetchAsset, fetchAssetWithPlugins, - fetchHashedAsset, - getAssetAccountDataSerializer, updateAuthority, } from '../src'; import { createUmi } from './_setup'; @@ -76,7 +74,7 @@ test('it can create a new asset in ledger state', async (t) => { const assetAddress = generateSigner(umi); // When we create a new account. - const txResult = await create(umi, { + const result = create(umi, { dataState: DataState.LedgerState, asset: assetAddress, name: 'Test Bread', @@ -85,27 +83,29 @@ test('it can create a new asset in ledger state', async (t) => { plugins: [], }).sendAndConfirm(umi); + await t.throwsAsync(result, { name: 'NotImplemented' }); + // Then an account was created with the correct data. - const asset = await fetchHashedAsset(umi, assetAddress.publicKey); + //const asset = await fetchHashedAsset(umi, assetAddress.publicKey); // console.log(asset); - t.like(asset, { - publicKey: assetAddress.publicKey, - }); - - const tx = await umi.rpc.getTransaction(txResult.signature); - if (tx && tx.meta.innerInstructions) { - // console.log(tx.meta.innerInstructions[0].instructions); - const { data } = tx.meta.innerInstructions[0].instructions[0]; - // console.log(base58.deserialize(data)); - const parsed = getAssetAccountDataSerializer().deserialize(data)[0]; - // console.log("Ledger State:", parsed); - t.like(parsed, { - updateAuthority: updateAuthority('Address', [umi.identity.publicKey]), - owner: umi.identity.publicKey, - name: 'Test Bread', - uri: 'https://example.com/bread', - }); - } + // t.like(asset, { + // publicKey: assetAddress.publicKey, + // }); + + // const tx = await umi.rpc.getTransaction(txResult.signature); + // if (tx && tx.meta.innerInstructions) { + // // console.log(tx.meta.innerInstructions[0].instructions); + // const { data } = tx.meta.innerInstructions[0].instructions[0]; + // // console.log(base58.deserialize(data)); + // const parsed = getAssetAccountDataSerializer().deserialize(data)[0]; + // // console.log("Ledger State:", parsed); + // t.like(parsed, { + // updateAuthority: updateAuthority('Address', [umi.identity.publicKey]), + // owner: umi.identity.publicKey, + // name: 'Test Bread', + // uri: 'https://example.com/bread', + // }); + // } }); test('it can create a new asset in account state with plugins', async (t) => { diff --git a/clients/js/test/decompress.test.ts b/clients/js/test/decompress.test.ts index 6b800948..c3c3f951 100644 --- a/clients/js/test/decompress.test.ts +++ b/clients/js/test/decompress.test.ts @@ -1,4 +1,4 @@ -import { generateSigner } from '@metaplex-foundation/umi'; +import { generateSigner, publicKey } from '@metaplex-foundation/umi'; import test from 'ava'; import { Asset, @@ -44,7 +44,8 @@ test('it can decompress a previously compressed asset as the owner', async (t) = // And when we compress the asset. await compress(umi, { asset: assetAddress.publicKey, - owner: umi.identity, + authority: umi.identity, + logWrapper: publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'), }).sendAndConfirm(umi); // And the asset is now compressed as a hashed asset. @@ -67,7 +68,7 @@ test('it can decompress a previously compressed asset as the owner', async (t) = // And when we decompress the asset. await decompress(umi, { asset: assetAddress.publicKey, - owner: umi.identity, + authority: umi.identity, compressionProof: { updateAuthority: updateAuthority('Address', [umi.identity.publicKey]), owner: umi.identity.publicKey, diff --git a/clients/js/test/info.test.ts b/clients/js/test/info.test.ts index 9ae35975..11d61696 100644 --- a/clients/js/test/info.test.ts +++ b/clients/js/test/info.test.ts @@ -1,6 +1,6 @@ import { generateSigner, publicKey } from '@metaplex-foundation/umi'; import test from 'ava'; -import { DataState, create /* fetchAsset, fetchHashedAsset */ } from '../src'; +import { DataState, create, compress /* fetchAsset, fetchHashedAsset */ } from '../src'; import { createUmi } from './_setup'; test('fetch account info for account state', async (t) => { @@ -30,14 +30,14 @@ test('fetch account info for account state', async (t) => { t.pass(); }); -test('fetch account info for ledger state', async (t) => { +test('HELLO EHLO HLEOOO fetch account info for ledger state', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); const assetAddress = generateSigner(umi); // When we create a new account. await create(umi, { - dataState: DataState.LedgerState, + dataState: DataState.AccountState, asset: assetAddress, name: 'Test Bread', uri: 'https://example.com/bread', @@ -45,6 +45,12 @@ test('fetch account info for ledger state', async (t) => { plugins: [], }).sendAndConfirm(umi); + // And when we compress the asset. + await compress(umi, { + asset: assetAddress.publicKey, + logWrapper: publicKey('noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV'), + }).sendAndConfirm(umi); + // Print the size of the account. const account = await umi.rpc.getAccount(assetAddress.publicKey); if (account.exists) { diff --git a/clients/rust/src/generated/errors/mpl_core.rs b/clients/rust/src/generated/errors/mpl_core.rs index 83a89a41..d512c00b 100644 --- a/clients/rust/src/generated/errors/mpl_core.rs +++ b/clients/rust/src/generated/errors/mpl_core.rs @@ -76,6 +76,12 @@ pub enum MplCoreError { /// 21 (0x15) - Missing new owner #[error("Missing new owner")] MissingNewOwner, + /// 22 (0x16) - Missing system program + #[error("Missing system program")] + MissingSystemProgram, + /// 23 (0x17) - Not implemented + #[error("Not implemented")] + NotImplemented, } impl solana_program::program_error::PrintProgramError for MplCoreError { diff --git a/clients/rust/src/generated/instructions/burn.rs b/clients/rust/src/generated/instructions/burn.rs index db5b4958..d1e932d9 100644 --- a/clients/rust/src/generated/instructions/burn.rs +++ b/clients/rust/src/generated/instructions/burn.rs @@ -19,6 +19,8 @@ pub struct Burn { pub authority: solana_program::pubkey::Pubkey, /// The account paying for the storage fees pub payer: Option, + /// The system program + pub system_program: Option, /// The SPL Noop Program pub log_wrapper: Option, } @@ -36,7 +38,7 @@ impl Burn { args: BurnInstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( self.asset, false, )); @@ -62,6 +64,17 @@ impl Burn { false, )); } + if let Some(system_program) = self.system_program { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + system_program, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } if let Some(log_wrapper) = self.log_wrapper { accounts.push(solana_program::instruction::AccountMeta::new_readonly( log_wrapper, @@ -111,13 +124,15 @@ pub struct BurnInstructionArgs { /// 1. `[writable, optional]` collection /// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer -/// 4. `[optional]` log_wrapper +/// 4. `[optional]` system_program +/// 5. `[optional]` log_wrapper #[derive(Default)] pub struct BurnBuilder { asset: Option, collection: Option, authority: Option, payer: Option, + system_program: Option, log_wrapper: Option, compression_proof: Option, __remaining_accounts: Vec, @@ -154,6 +169,16 @@ impl BurnBuilder { self } /// `[optional account]` + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: Option, + ) -> &mut Self { + self.system_program = system_program; + self + } + /// `[optional account]` /// The SPL Noop Program #[inline(always)] pub fn log_wrapper( @@ -194,6 +219,7 @@ impl BurnBuilder { collection: self.collection, authority: self.authority.expect("authority is not set"), payer: self.payer, + system_program: self.system_program, log_wrapper: self.log_wrapper, }; let args = BurnInstructionArgs { @@ -214,6 +240,8 @@ pub struct BurnCpiAccounts<'a, 'b> { pub authority: &'b solana_program::account_info::AccountInfo<'a>, /// The account paying for the storage fees pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The SPL Noop Program pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, } @@ -230,6 +258,8 @@ pub struct BurnCpi<'a, 'b> { pub authority: &'b solana_program::account_info::AccountInfo<'a>, /// The account paying for the storage fees pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The SPL Noop Program pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The arguments for the instruction. @@ -248,6 +278,7 @@ impl<'a, 'b> BurnCpi<'a, 'b> { collection: accounts.collection, authority: accounts.authority, payer: accounts.payer, + system_program: accounts.system_program, log_wrapper: accounts.log_wrapper, __args: args, } @@ -285,7 +316,7 @@ impl<'a, 'b> BurnCpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( *self.asset.key, false, @@ -315,6 +346,17 @@ impl<'a, 'b> BurnCpi<'a, 'b> { false, )); } + if let Some(system_program) = self.system_program { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *system_program.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } if let Some(log_wrapper) = self.log_wrapper { accounts.push(solana_program::instruction::AccountMeta::new_readonly( *log_wrapper.key, @@ -342,7 +384,7 @@ impl<'a, 'b> BurnCpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.asset.clone()); if let Some(collection) = self.collection { @@ -352,6 +394,9 @@ impl<'a, 'b> BurnCpi<'a, 'b> { if let Some(payer) = self.payer { account_infos.push(payer.clone()); } + if let Some(system_program) = self.system_program { + account_infos.push(system_program.clone()); + } if let Some(log_wrapper) = self.log_wrapper { account_infos.push(log_wrapper.clone()); } @@ -375,7 +420,8 @@ impl<'a, 'b> BurnCpi<'a, 'b> { /// 1. `[writable, optional]` collection /// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer -/// 4. `[optional]` log_wrapper +/// 4. `[optional]` system_program +/// 5. `[optional]` log_wrapper pub struct BurnCpiBuilder<'a, 'b> { instruction: Box>, } @@ -388,6 +434,7 @@ impl<'a, 'b> BurnCpiBuilder<'a, 'b> { collection: None, authority: None, payer: None, + system_program: None, log_wrapper: None, compression_proof: None, __remaining_accounts: Vec::new(), @@ -430,6 +477,16 @@ impl<'a, 'b> BurnCpiBuilder<'a, 'b> { self } /// `[optional account]` + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.system_program = system_program; + self + } + /// `[optional account]` /// The SPL Noop Program #[inline(always)] pub fn log_wrapper( @@ -500,6 +557,8 @@ impl<'a, 'b> BurnCpiBuilder<'a, 'b> { payer: self.instruction.payer, + system_program: self.instruction.system_program, + log_wrapper: self.instruction.log_wrapper, __args: args, }; @@ -516,6 +575,7 @@ struct BurnCpiBuilderInstruction<'a, 'b> { collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, compression_proof: Option, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. diff --git a/clients/rust/src/generated/instructions/compress.rs b/clients/rust/src/generated/instructions/compress.rs index 2d3d0ad1..fc49ede1 100644 --- a/clients/rust/src/generated/instructions/compress.rs +++ b/clients/rust/src/generated/instructions/compress.rs @@ -15,7 +15,7 @@ pub struct Compress { /// The collection to which the asset belongs pub collection: Option, /// The owner or delegate of the asset - pub owner: solana_program::pubkey::Pubkey, + pub authority: solana_program::pubkey::Pubkey, /// The account receiving the storage fees pub payer: Option, /// The system program @@ -48,7 +48,8 @@ impl Compress { )); } accounts.push(solana_program::instruction::AccountMeta::new_readonly( - self.owner, true, + self.authority, + true, )); if let Some(payer) = self.payer { accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); @@ -101,7 +102,7 @@ impl CompressInstructionData { /// /// 0. `[writable]` asset /// 1. `[optional]` collection -/// 2. `[signer]` owner +/// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer /// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) /// 5. `[optional]` log_wrapper @@ -109,7 +110,7 @@ impl CompressInstructionData { pub struct CompressBuilder { asset: Option, collection: Option, - owner: Option, + authority: Option, payer: Option, system_program: Option, log_wrapper: Option, @@ -135,8 +136,8 @@ impl CompressBuilder { } /// The owner or delegate of the asset #[inline(always)] - pub fn owner(&mut self, owner: solana_program::pubkey::Pubkey) -> &mut Self { - self.owner = Some(owner); + pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { + self.authority = Some(authority); self } /// `[optional account]` @@ -186,7 +187,7 @@ impl CompressBuilder { let accounts = Compress { asset: self.asset.expect("asset is not set"), collection: self.collection, - owner: self.owner.expect("owner is not set"), + authority: self.authority.expect("authority is not set"), payer: self.payer, system_program: self .system_program @@ -205,7 +206,7 @@ pub struct CompressCpiAccounts<'a, 'b> { /// The collection to which the asset belongs pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The owner or delegate of the asset - pub owner: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: &'b solana_program::account_info::AccountInfo<'a>, /// The account receiving the storage fees pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program @@ -223,7 +224,7 @@ pub struct CompressCpi<'a, 'b> { /// The collection to which the asset belongs pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The owner or delegate of the asset - pub owner: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: &'b solana_program::account_info::AccountInfo<'a>, /// The account receiving the storage fees pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program @@ -241,7 +242,7 @@ impl<'a, 'b> CompressCpi<'a, 'b> { __program: program, asset: accounts.asset, collection: accounts.collection, - owner: accounts.owner, + authority: accounts.authority, payer: accounts.payer, system_program: accounts.system_program, log_wrapper: accounts.log_wrapper, @@ -297,7 +298,7 @@ impl<'a, 'b> CompressCpi<'a, 'b> { )); } accounts.push(solana_program::instruction::AccountMeta::new_readonly( - *self.owner.key, + *self.authority.key, true, )); if let Some(payer) = self.payer { @@ -345,7 +346,7 @@ impl<'a, 'b> CompressCpi<'a, 'b> { if let Some(collection) = self.collection { account_infos.push(collection.clone()); } - account_infos.push(self.owner.clone()); + account_infos.push(self.authority.clone()); if let Some(payer) = self.payer { account_infos.push(payer.clone()); } @@ -371,7 +372,7 @@ impl<'a, 'b> CompressCpi<'a, 'b> { /// /// 0. `[writable]` asset /// 1. `[optional]` collection -/// 2. `[signer]` owner +/// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer /// 4. `[]` system_program /// 5. `[optional]` log_wrapper @@ -385,7 +386,7 @@ impl<'a, 'b> CompressCpiBuilder<'a, 'b> { __program: program, asset: None, collection: None, - owner: None, + authority: None, payer: None, system_program: None, log_wrapper: None, @@ -411,8 +412,11 @@ impl<'a, 'b> CompressCpiBuilder<'a, 'b> { } /// The owner or delegate of the asset #[inline(always)] - pub fn owner(&mut self, owner: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { - self.instruction.owner = Some(owner); + pub fn authority( + &mut self, + authority: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.authority = Some(authority); self } /// `[optional account]` @@ -492,7 +496,7 @@ impl<'a, 'b> CompressCpiBuilder<'a, 'b> { collection: self.instruction.collection, - owner: self.instruction.owner.expect("owner is not set"), + authority: self.instruction.authority.expect("authority is not set"), payer: self.instruction.payer, @@ -514,7 +518,7 @@ struct CompressCpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, - owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/clients/rust/src/generated/instructions/decompress.rs b/clients/rust/src/generated/instructions/decompress.rs index 905efb54..93105efd 100644 --- a/clients/rust/src/generated/instructions/decompress.rs +++ b/clients/rust/src/generated/instructions/decompress.rs @@ -16,7 +16,7 @@ pub struct Decompress { /// The collection to which the asset belongs pub collection: Option, /// The owner or delegate of the asset - pub owner: solana_program::pubkey::Pubkey, + pub authority: solana_program::pubkey::Pubkey, /// The account paying for the storage fees pub payer: Option, /// The system program @@ -53,7 +53,8 @@ impl Decompress { )); } accounts.push(solana_program::instruction::AccountMeta::new_readonly( - self.owner, true, + self.authority, + true, )); if let Some(payer) = self.payer { accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); @@ -114,7 +115,7 @@ pub struct DecompressInstructionArgs { /// /// 0. `[writable]` asset /// 1. `[optional]` collection -/// 2. `[signer]` owner +/// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer /// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) /// 5. `[optional]` log_wrapper @@ -122,7 +123,7 @@ pub struct DecompressInstructionArgs { pub struct DecompressBuilder { asset: Option, collection: Option, - owner: Option, + authority: Option, payer: Option, system_program: Option, log_wrapper: Option, @@ -149,8 +150,8 @@ impl DecompressBuilder { } /// The owner or delegate of the asset #[inline(always)] - pub fn owner(&mut self, owner: solana_program::pubkey::Pubkey) -> &mut Self { - self.owner = Some(owner); + pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { + self.authority = Some(authority); self } /// `[optional account]` @@ -205,7 +206,7 @@ impl DecompressBuilder { let accounts = Decompress { asset: self.asset.expect("asset is not set"), collection: self.collection, - owner: self.owner.expect("owner is not set"), + authority: self.authority.expect("authority is not set"), payer: self.payer, system_program: self .system_program @@ -230,7 +231,7 @@ pub struct DecompressCpiAccounts<'a, 'b> { /// The collection to which the asset belongs pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The owner or delegate of the asset - pub owner: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: &'b solana_program::account_info::AccountInfo<'a>, /// The account paying for the storage fees pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program @@ -248,7 +249,7 @@ pub struct DecompressCpi<'a, 'b> { /// The collection to which the asset belongs pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The owner or delegate of the asset - pub owner: &'b solana_program::account_info::AccountInfo<'a>, + pub authority: &'b solana_program::account_info::AccountInfo<'a>, /// The account paying for the storage fees pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The system program @@ -269,7 +270,7 @@ impl<'a, 'b> DecompressCpi<'a, 'b> { __program: program, asset: accounts.asset, collection: accounts.collection, - owner: accounts.owner, + authority: accounts.authority, payer: accounts.payer, system_program: accounts.system_program, log_wrapper: accounts.log_wrapper, @@ -326,7 +327,7 @@ impl<'a, 'b> DecompressCpi<'a, 'b> { )); } accounts.push(solana_program::instruction::AccountMeta::new_readonly( - *self.owner.key, + *self.authority.key, true, )); if let Some(payer) = self.payer { @@ -376,7 +377,7 @@ impl<'a, 'b> DecompressCpi<'a, 'b> { if let Some(collection) = self.collection { account_infos.push(collection.clone()); } - account_infos.push(self.owner.clone()); + account_infos.push(self.authority.clone()); if let Some(payer) = self.payer { account_infos.push(payer.clone()); } @@ -402,7 +403,7 @@ impl<'a, 'b> DecompressCpi<'a, 'b> { /// /// 0. `[writable]` asset /// 1. `[optional]` collection -/// 2. `[signer]` owner +/// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer /// 4. `[]` system_program /// 5. `[optional]` log_wrapper @@ -416,7 +417,7 @@ impl<'a, 'b> DecompressCpiBuilder<'a, 'b> { __program: program, asset: None, collection: None, - owner: None, + authority: None, payer: None, system_program: None, log_wrapper: None, @@ -443,8 +444,11 @@ impl<'a, 'b> DecompressCpiBuilder<'a, 'b> { } /// The owner or delegate of the asset #[inline(always)] - pub fn owner(&mut self, owner: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { - self.instruction.owner = Some(owner); + pub fn authority( + &mut self, + authority: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.authority = Some(authority); self } /// `[optional account]` @@ -536,7 +540,7 @@ impl<'a, 'b> DecompressCpiBuilder<'a, 'b> { collection: self.instruction.collection, - owner: self.instruction.owner.expect("owner is not set"), + authority: self.instruction.authority.expect("authority is not set"), payer: self.instruction.payer, @@ -559,7 +563,7 @@ struct DecompressCpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, asset: Option<&'b solana_program::account_info::AccountInfo<'a>>, collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, - owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/clients/rust/src/generated/instructions/transfer.rs b/clients/rust/src/generated/instructions/transfer.rs index 5cda0016..9b0288de 100644 --- a/clients/rust/src/generated/instructions/transfer.rs +++ b/clients/rust/src/generated/instructions/transfer.rs @@ -21,6 +21,8 @@ pub struct Transfer { pub payer: Option, /// The new owner to which to transfer the asset pub new_owner: solana_program::pubkey::Pubkey, + /// The system program + pub system_program: Option, /// The SPL Noop Program pub log_wrapper: Option, } @@ -38,7 +40,7 @@ impl Transfer { args: TransferInstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { - let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( self.asset, false, )); @@ -68,6 +70,17 @@ impl Transfer { self.new_owner, false, )); + if let Some(system_program) = self.system_program { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + system_program, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } if let Some(log_wrapper) = self.log_wrapper { accounts.push(solana_program::instruction::AccountMeta::new_readonly( log_wrapper, @@ -118,7 +131,8 @@ pub struct TransferInstructionArgs { /// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer /// 4. `[]` new_owner -/// 5. `[optional]` log_wrapper +/// 5. `[optional]` system_program +/// 6. `[optional]` log_wrapper #[derive(Default)] pub struct TransferBuilder { asset: Option, @@ -126,6 +140,7 @@ pub struct TransferBuilder { authority: Option, payer: Option, new_owner: Option, + system_program: Option, log_wrapper: Option, compression_proof: Option, __remaining_accounts: Vec, @@ -168,6 +183,16 @@ impl TransferBuilder { self } /// `[optional account]` + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: Option, + ) -> &mut Self { + self.system_program = system_program; + self + } + /// `[optional account]` /// The SPL Noop Program #[inline(always)] pub fn log_wrapper( @@ -209,6 +234,7 @@ impl TransferBuilder { authority: self.authority.expect("authority is not set"), payer: self.payer, new_owner: self.new_owner.expect("new_owner is not set"), + system_program: self.system_program, log_wrapper: self.log_wrapper, }; let args = TransferInstructionArgs { @@ -231,6 +257,8 @@ pub struct TransferCpiAccounts<'a, 'b> { pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The new owner to which to transfer the asset pub new_owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The SPL Noop Program pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, } @@ -249,6 +277,8 @@ pub struct TransferCpi<'a, 'b> { pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The new owner to which to transfer the asset pub new_owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The SPL Noop Program pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The arguments for the instruction. @@ -268,6 +298,7 @@ impl<'a, 'b> TransferCpi<'a, 'b> { authority: accounts.authority, payer: accounts.payer, new_owner: accounts.new_owner, + system_program: accounts.system_program, log_wrapper: accounts.log_wrapper, __args: args, } @@ -305,7 +336,7 @@ impl<'a, 'b> TransferCpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( *self.asset.key, false, @@ -339,6 +370,17 @@ impl<'a, 'b> TransferCpi<'a, 'b> { *self.new_owner.key, false, )); + if let Some(system_program) = self.system_program { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *system_program.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_CORE_ID, + false, + )); + } if let Some(log_wrapper) = self.log_wrapper { accounts.push(solana_program::instruction::AccountMeta::new_readonly( *log_wrapper.key, @@ -366,7 +408,7 @@ impl<'a, 'b> TransferCpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(7 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.asset.clone()); if let Some(collection) = self.collection { @@ -377,6 +419,9 @@ impl<'a, 'b> TransferCpi<'a, 'b> { account_infos.push(payer.clone()); } account_infos.push(self.new_owner.clone()); + if let Some(system_program) = self.system_program { + account_infos.push(system_program.clone()); + } if let Some(log_wrapper) = self.log_wrapper { account_infos.push(log_wrapper.clone()); } @@ -401,7 +446,8 @@ impl<'a, 'b> TransferCpi<'a, 'b> { /// 2. `[signer]` authority /// 3. `[writable, signer, optional]` payer /// 4. `[]` new_owner -/// 5. `[optional]` log_wrapper +/// 5. `[optional]` system_program +/// 6. `[optional]` log_wrapper pub struct TransferCpiBuilder<'a, 'b> { instruction: Box>, } @@ -415,6 +461,7 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { authority: None, payer: None, new_owner: None, + system_program: None, log_wrapper: None, compression_proof: None, __remaining_accounts: Vec::new(), @@ -466,6 +513,16 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { self } /// `[optional account]` + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.system_program = system_program; + self + } + /// `[optional account]` /// The SPL Noop Program #[inline(always)] pub fn log_wrapper( @@ -538,6 +595,8 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { new_owner: self.instruction.new_owner.expect("new_owner is not set"), + system_program: self.instruction.system_program, + log_wrapper: self.instruction.log_wrapper, __args: args, }; @@ -555,6 +614,7 @@ struct TransferCpiBuilderInstruction<'a, 'b> { authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, new_owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, compression_proof: Option, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. diff --git a/idls/mpl_core.json b/idls/mpl_core.json index ac21832b..009eb7e4 100644 --- a/idls/mpl_core.json +++ b/idls/mpl_core.json @@ -819,6 +819,15 @@ "The account paying for the storage fees" ] }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The system program" + ] + }, { "name": "logWrapper", "isMut": false, @@ -938,6 +947,15 @@ "The new owner to which to transfer the asset" ] }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The system program" + ] + }, { "name": "logWrapper", "isMut": false, @@ -1127,7 +1145,7 @@ ] }, { - "name": "owner", + "name": "authority", "isMut": false, "isSigner": true, "docs": [ @@ -1195,7 +1213,7 @@ ] }, { - "name": "owner", + "name": "authority", "isMut": false, "isSigner": true, "docs": [ @@ -2275,6 +2293,16 @@ "code": 21, "name": "MissingNewOwner", "msg": "Missing new owner" + }, + { + "code": 22, + "name": "MissingSystemProgram", + "msg": "Missing system program" + }, + { + "code": 23, + "name": "NotImplemented", + "msg": "Not implemented" } ], "metadata": { diff --git a/programs/mpl-core/src/error.rs b/programs/mpl-core/src/error.rs index f9464fc7..6b8ef529 100644 --- a/programs/mpl-core/src/error.rs +++ b/programs/mpl-core/src/error.rs @@ -96,6 +96,14 @@ pub enum MplCoreError { /// 20 - Missing new owner #[error("Missing new owner")] MissingNewOwner, + + /// 21 - Missing system program + #[error("Missing system program")] + MissingSystemProgram, + + /// 22 - Not implemented + #[error("Not implemented")] + NotImplemented, } impl PrintProgramError for MplCoreError { diff --git a/programs/mpl-core/src/instruction.rs b/programs/mpl-core/src/instruction.rs index ff25b42e..07d4f314 100644 --- a/programs/mpl-core/src/instruction.rs +++ b/programs/mpl-core/src/instruction.rs @@ -125,7 +125,8 @@ pub(crate) enum MplAssetInstruction { #[account(1, optional, writable, name="collection", desc = "The collection to which the asset belongs")] #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] #[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")] - #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] + #[account(4, optional, name="system_program", desc = "The system program")] + #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] Burn(BurnArgs), /// Burn an mpl-core. @@ -142,7 +143,8 @@ pub(crate) enum MplAssetInstruction { #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] #[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")] #[account(4, name="new_owner", desc = "The new owner to which to transfer the asset")] - #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + #[account(5, optional, name="system_program", desc = "The system program")] + #[account(6, optional, name="log_wrapper", desc = "The SPL Noop Program")] Transfer(TransferArgs), /// Update an mpl-core. @@ -164,21 +166,19 @@ pub(crate) enum MplAssetInstruction { #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] UpdateCollection(UpdateCollectionArgs), - /// Create a new mpl-core. - /// This function creates the initial mpl-core + /// Compress an mpl-core. #[account(0, writable, name="asset", desc = "The address of the asset")] #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] - #[account(2, signer, name="owner", desc = "The owner or delegate of the asset")] + #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] #[account(3, optional, writable, signer, name="payer", desc = "The account receiving the storage fees")] #[account(4, name="system_program", desc = "The system program")] #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] Compress(CompressArgs), - /// Create a new mpl-core. - /// This function creates the initial mpl-core + /// Decompress an mpl-core. #[account(0, writable, name="asset", desc = "The address of the asset")] #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] - #[account(2, signer, name="owner", desc = "The owner or delegate of the asset")] + #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] #[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")] #[account(4, name="system_program", desc = "The system program")] #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] diff --git a/programs/mpl-core/src/plugins/burn.rs b/programs/mpl-core/src/plugins/burn.rs index f7ff798b..0f6d7078 100644 --- a/programs/mpl-core/src/plugins/burn.rs +++ b/programs/mpl-core/src/plugins/burn.rs @@ -1,10 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{account_info::AccountInfo, program_error::ProgramError}; -use crate::{ - processor::{CompressArgs, CreateArgs, DecompressArgs}, - state::{Authority, DataBlob}, -}; +use crate::state::{Authority, DataBlob}; use super::{PluginValidation, ValidationResult}; @@ -41,7 +38,6 @@ impl PluginValidation for Burn { fn validate_create( &self, _authority_info: &AccountInfo, - _args: &CreateArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -83,7 +79,6 @@ impl PluginValidation for Burn { fn validate_compress( &self, _authority_info: &AccountInfo, - _args: &CompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -92,7 +87,6 @@ impl PluginValidation for Burn { fn validate_decompress( &self, _authority_info: &AccountInfo, - _args: &DecompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/freeze.rs b/programs/mpl-core/src/plugins/freeze.rs index 25c553b0..e63338a5 100644 --- a/programs/mpl-core/src/plugins/freeze.rs +++ b/programs/mpl-core/src/plugins/freeze.rs @@ -1,10 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{account_info::AccountInfo, program_error::ProgramError}; -use crate::{ - processor::{ApprovePluginAuthorityArgs, CompressArgs, CreateArgs, DecompressArgs}, - state::{Asset, Authority, DataBlob}, -}; +use crate::state::{Asset, Authority, DataBlob}; use super::{PluginValidation, ValidationResult}; @@ -44,7 +41,6 @@ impl PluginValidation for Freeze { fn validate_create( &self, _authority_info: &AccountInfo, - _args: &CreateArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -109,7 +105,6 @@ impl PluginValidation for Freeze { fn validate_compress( &self, _authority_info: &AccountInfo, - _args: &CompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -118,7 +113,6 @@ impl PluginValidation for Freeze { fn validate_decompress( &self, _authority_info: &AccountInfo, - _args: &DecompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -127,7 +121,6 @@ impl PluginValidation for Freeze { fn validate_add_authority( &self, _authority_info: &AccountInfo, - _args: &ApprovePluginAuthorityArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index 0ccb934e..ce3fef81 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -4,10 +4,6 @@ use solana_program::{account_info::AccountInfo, program_error::ProgramError}; use crate::{ error::MplCoreError, - processor::{ - ApprovePluginAuthorityArgs, CompressArgs, CreateArgs, DecompressArgs, - RevokePluginAuthorityArgs, - }, state::{Asset, Authority, Key}, }; @@ -84,17 +80,17 @@ impl Plugin { pub(crate) fn validate_create( plugin: &Plugin, authority: &AccountInfo, - args: &CreateArgs, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), - Plugin::Royalties(royalties) => royalties.validate_create(authority, args, authorities), - Plugin::Freeze(freeze) => freeze.validate_create(authority, args, authorities), - Plugin::Burn(burn) => burn.validate_create(authority, args, authorities), - Plugin::Transfer(transfer) => transfer.validate_create(authority, args, authorities), + Plugin::Royalties(royalties) => royalties.validate_create(authority, authorities), + Plugin::Freeze(freeze) => freeze.validate_create(authority, authorities), + Plugin::Burn(burn) => burn.validate_create(authority, authorities), + Plugin::Transfer(transfer) => transfer.validate_create(authority, authorities), Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_create(authority, args, authorities) + update_delegate.validate_create(authority, authorities) } } } @@ -124,6 +120,7 @@ impl Plugin { plugin: &Plugin, asset: &Asset, authority: &AccountInfo, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { @@ -189,19 +186,17 @@ impl Plugin { pub(crate) fn validate_compress( plugin: &Plugin, authority: &AccountInfo, - args: &CompressArgs, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), - Plugin::Royalties(royalties) => { - royalties.validate_compress(authority, args, authorities) - } - Plugin::Freeze(freeze) => freeze.validate_compress(authority, args, authorities), - Plugin::Burn(burn) => burn.validate_compress(authority, args, authorities), - Plugin::Transfer(transfer) => transfer.validate_compress(authority, args, authorities), + Plugin::Royalties(royalties) => royalties.validate_compress(authority, authorities), + Plugin::Freeze(freeze) => freeze.validate_compress(authority, authorities), + Plugin::Burn(burn) => burn.validate_compress(authority, authorities), + Plugin::Transfer(transfer) => transfer.validate_compress(authority, authorities), Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_compress(authority, args, authorities) + update_delegate.validate_compress(authority, authorities) } } } @@ -210,21 +205,17 @@ impl Plugin { pub(crate) fn validate_decompress( plugin: &Plugin, authority: &AccountInfo, - args: &DecompressArgs, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), - Plugin::Royalties(royalties) => { - royalties.validate_decompress(authority, args, authorities) - } - Plugin::Freeze(freeze) => freeze.validate_decompress(authority, args, authorities), - Plugin::Burn(burn) => burn.validate_decompress(authority, args, authorities), - Plugin::Transfer(transfer) => { - transfer.validate_decompress(authority, args, authorities) - } + Plugin::Royalties(royalties) => royalties.validate_decompress(authority, authorities), + Plugin::Freeze(freeze) => freeze.validate_decompress(authority, authorities), + Plugin::Burn(burn) => burn.validate_decompress(authority, authorities), + Plugin::Transfer(transfer) => transfer.validate_decompress(authority, authorities), Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_decompress(authority, args, authorities) + update_delegate.validate_decompress(authority, authorities) } } } @@ -234,21 +225,19 @@ impl Plugin { pub(crate) fn validate_add_plugin_authority( plugin: &Plugin, authority: &AccountInfo, - args: &ApprovePluginAuthorityArgs, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), Plugin::Royalties(royalties) => { - royalties.validate_add_authority(authority, args, authorities) - } - Plugin::Freeze(freeze) => freeze.validate_add_authority(authority, args, authorities), - Plugin::Burn(burn) => burn.validate_add_authority(authority, args, authorities), - Plugin::Transfer(transfer) => { - transfer.validate_add_authority(authority, args, authorities) + royalties.validate_add_authority(authority, authorities) } + Plugin::Freeze(freeze) => freeze.validate_add_authority(authority, authorities), + Plugin::Burn(burn) => burn.validate_add_authority(authority, authorities), + Plugin::Transfer(transfer) => transfer.validate_add_authority(authority, authorities), Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_add_authority(authority, args, authorities) + update_delegate.validate_add_authority(authority, authorities) } } } @@ -258,23 +247,21 @@ impl Plugin { pub(crate) fn validate_remove_plugin_authority( plugin: &Plugin, authority: &AccountInfo, - args: &RevokePluginAuthorityArgs, + _unused: Option<&AccountInfo>, authorities: &Authority, ) -> Result { match plugin { Plugin::Reserved => Err(MplCoreError::InvalidPlugin.into()), Plugin::Royalties(royalties) => { - royalties.validate_remove_authority(authority, args, authorities) - } - Plugin::Freeze(freeze) => { - freeze.validate_remove_authority(authority, args, authorities) + royalties.validate_remove_authority(authority, authorities) } - Plugin::Burn(burn) => burn.validate_remove_authority(authority, args, authorities), + Plugin::Freeze(freeze) => freeze.validate_remove_authority(authority, authorities), + Plugin::Burn(burn) => burn.validate_remove_authority(authority, authorities), Plugin::Transfer(transfer) => { - transfer.validate_remove_authority(authority, args, authorities) + transfer.validate_remove_authority(authority, authorities) } Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_remove_authority(authority, args, authorities) + update_delegate.validate_remove_authority(authority, authorities) } } } @@ -298,7 +285,6 @@ pub(crate) trait PluginValidation { fn validate_create( &self, authority: &AccountInfo, - args: &CreateArgs, authorities: &Authority, ) -> Result; @@ -349,7 +335,6 @@ pub(crate) trait PluginValidation { fn validate_compress( &self, authority: &AccountInfo, - args: &CompressArgs, authorities: &Authority, ) -> Result; @@ -357,7 +342,6 @@ pub(crate) trait PluginValidation { fn validate_decompress( &self, authority: &AccountInfo, - args: &DecompressArgs, authorities: &Authority, ) -> Result; @@ -365,7 +349,6 @@ pub(crate) trait PluginValidation { fn validate_add_authority( &self, _authority: &AccountInfo, - _args: &ApprovePluginAuthorityArgs, _authorities: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -375,7 +358,6 @@ pub(crate) trait PluginValidation { fn validate_remove_authority( &self, _authority: &AccountInfo, - _args: &RevokePluginAuthorityArgs, _authorities: &Authority, ) -> Result { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/royalties.rs b/programs/mpl-core/src/plugins/royalties.rs index 3dafa3c2..d1135687 100644 --- a/programs/mpl-core/src/plugins/royalties.rs +++ b/programs/mpl-core/src/plugins/royalties.rs @@ -1,10 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; -use crate::{ - processor::{CompressArgs, CreateArgs, DecompressArgs}, - state::Authority, -}; +use crate::state::Authority; use super::{PluginValidation, ValidationResult}; @@ -41,7 +38,6 @@ impl PluginValidation for Royalties { fn validate_create( &self, _authority_info: &AccountInfo, - _args: &CreateArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -93,7 +89,6 @@ impl PluginValidation for Royalties { fn validate_compress( &self, _authority_info: &AccountInfo, - _args: &CompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -102,7 +97,6 @@ impl PluginValidation for Royalties { fn validate_decompress( &self, _authority_info: &AccountInfo, - _args: &DecompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/transfer.rs b/programs/mpl-core/src/plugins/transfer.rs index ceab7332..f5aa42f0 100644 --- a/programs/mpl-core/src/plugins/transfer.rs +++ b/programs/mpl-core/src/plugins/transfer.rs @@ -1,10 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{account_info::AccountInfo, program_error::ProgramError}; -use crate::{ - processor::{CompressArgs, CreateArgs, DecompressArgs}, - state::{Authority, DataBlob}, -}; +use crate::state::{Authority, DataBlob}; use super::{PluginValidation, ValidationResult}; @@ -41,7 +38,6 @@ impl PluginValidation for Transfer { fn validate_create( &self, _authority_info: &AccountInfo, - _args: &CreateArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -91,7 +87,6 @@ impl PluginValidation for Transfer { fn validate_compress( &self, _authority_info: &AccountInfo, - _args: &CompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -100,7 +95,6 @@ impl PluginValidation for Transfer { fn validate_decompress( &self, _authority_info: &AccountInfo, - _args: &DecompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/update_delegate.rs b/programs/mpl-core/src/plugins/update_delegate.rs index 6c4ae82b..4402f433 100644 --- a/programs/mpl-core/src/plugins/update_delegate.rs +++ b/programs/mpl-core/src/plugins/update_delegate.rs @@ -1,10 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{account_info::AccountInfo, program_error::ProgramError}; -use crate::{ - processor::{CompressArgs, CreateArgs, DecompressArgs}, - state::{Authority, DataBlob}, -}; +use crate::state::{Authority, DataBlob}; use super::{PluginValidation, ValidationResult}; @@ -41,7 +38,6 @@ impl PluginValidation for UpdateDelegate { fn validate_create( &self, _authority_info: &AccountInfo, - _args: &CreateArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -75,7 +71,6 @@ impl PluginValidation for UpdateDelegate { fn validate_compress( &self, _authority_info: &AccountInfo, - _args: &CompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) @@ -84,7 +79,6 @@ impl PluginValidation for UpdateDelegate { fn validate_decompress( &self, _authority_info: &AccountInfo, - _args: &DecompressArgs, _authority: &Authority, ) -> Result { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/processor/burn.rs b/programs/mpl-core/src/processor/burn.rs index fec4ee0d..aedc40c3 100644 --- a/programs/mpl-core/src/processor/burn.rs +++ b/programs/mpl-core/src/processor/burn.rs @@ -6,10 +6,10 @@ use crate::{ error::MplCoreError, instruction::accounts::{BurnAccounts, BurnCollectionAccounts}, plugins::{Plugin, PluginType}, - state::{Asset, Collection, Compressible, CompressionProof, Key}, + state::{Asset, Collection, CompressionProof, Key}, utils::{ - close_program_account, load_key, validate_asset_permissions, - validate_collection_permissions, verify_proof, + close_program_account, load_key, rebuild_account_state_from_proof_data, + validate_asset_permissions, validate_collection_permissions, verify_proof, }, }; @@ -24,42 +24,54 @@ pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnArgs) -> Progr // Guards. assert_signer(ctx.accounts.authority)?; - if let Some(payer) = ctx.accounts.payer { + let payer = if let Some(payer) = ctx.accounts.payer { assert_signer(payer)?; - } + payer + } else { + ctx.accounts.authority + }; match load_key(ctx.accounts.asset, 0)? { Key::HashedAsset => { let compression_proof = args .compression_proof .ok_or(MplCoreError::MissingCompressionProof)?; - let (asset, _) = verify_proof(ctx.accounts.asset, &compression_proof)?; - if ctx.accounts.authority.key != &asset.owner { - return Err(MplCoreError::InvalidAuthority.into()); - } + let system_program = ctx + .accounts + .system_program + .ok_or(MplCoreError::MissingSystemProgram)?; - // TODO: Check delegates in compressed case. + // Verify the proof and rebuild Asset struct in account space. + let (asset, plugins) = verify_proof(ctx.accounts.asset, &compression_proof)?; - asset.wrap()?; - } - Key::Asset => { - let _ = validate_asset_permissions( - ctx.accounts.authority, + // Use the data from the compression proof to rebuild the account. Only needed for validation. + rebuild_account_state_from_proof_data( + asset, + plugins, ctx.accounts.asset, - ctx.accounts.collection, - None, - Asset::check_burn, - Collection::check_burn, - PluginType::check_burn, - Asset::validate_burn, - Collection::validate_burn, - Plugin::validate_burn, + payer, + system_program, )?; } + Key::Asset => (), _ => return Err(MplCoreError::IncorrectAccount.into()), } + // Validate asset permissions. + let _ = validate_asset_permissions( + ctx.accounts.authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + Asset::check_burn, + Collection::check_burn, + PluginType::check_burn, + Asset::validate_burn, + Collection::validate_burn, + Plugin::validate_burn, + )?; + process_burn(ctx.accounts.asset, ctx.accounts.authority) } @@ -81,6 +93,7 @@ pub(crate) fn burn_collection<'a>( assert_signer(payer)?; } + // Validate collection permissions. let _ = validate_collection_permissions( ctx.accounts.authority, ctx.accounts.collection, diff --git a/programs/mpl-core/src/processor/compress.rs b/programs/mpl-core/src/processor/compress.rs index 2c94fb0f..9dc79285 100644 --- a/programs/mpl-core/src/processor/compress.rs +++ b/programs/mpl-core/src/processor/compress.rs @@ -1,127 +1,63 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplCoreError, instruction::accounts::CompressAccounts, - plugins::{CheckResult, Plugin, PluginType, RegistryRecord, ValidationResult}, - state::{Asset, Compressible, HashablePluginSchema, HashedAsset, HashedAssetSchema, Key}, - utils::{fetch_core_data, load_key, resize_or_reallocate_account}, + plugins::{Plugin, PluginType}, + state::{Asset, Collection, Key, Wrappable}, + utils::{compress_into_account_space, fetch_core_data, load_key, validate_asset_permissions}, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct CompressArgs {} -pub(crate) fn compress<'a>(accounts: &'a [AccountInfo<'a>], args: CompressArgs) -> ProgramResult { +pub(crate) fn compress<'a>(accounts: &'a [AccountInfo<'a>], _args: CompressArgs) -> ProgramResult { // Accounts. let ctx = CompressAccounts::context(accounts)?; // Guards. - assert_signer(ctx.accounts.owner)?; + assert_signer(ctx.accounts.authority)?; let payer = if let Some(payer) = ctx.accounts.payer { assert_signer(payer)?; payer } else { - ctx.accounts.owner + ctx.accounts.authority }; match load_key(ctx.accounts.asset, 0)? { Key::Asset => { let (asset, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; - let mut approved = false; - match Asset::check_compress() { - CheckResult::CanApprove | CheckResult::CanReject => { - match asset.validate_compress(&ctx.accounts)? { - ValidationResult::Approved => { - approved = true; - } - ValidationResult::Rejected => { - return Err(MplCoreError::InvalidAuthority.into()) - } - ValidationResult::Pass => (), - } - } - CheckResult::None => (), - }; - - if let Some(plugin_registry) = &plugin_registry { - for record in &plugin_registry.registry { - if matches!( - PluginType::check_compress(&record.plugin_type), - CheckResult::CanApprove | CheckResult::CanReject - ) { - let result = Plugin::validate_compress( - &Plugin::load(ctx.accounts.asset, record.offset)?, - ctx.accounts.owner, - &args, - &record.authority, - )?; - if result == ValidationResult::Rejected { - return Err(MplCoreError::InvalidAuthority.into()); - } else if result == ValidationResult::Approved { - approved = true; - } - } - } - }; - - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } - - let mut plugin_hashes = vec![]; - if let Some(plugin_registry) = plugin_registry { - let mut registry_records = plugin_registry.registry; - - // It should already be sorted but we just want to make sure. - registry_records.sort_by(RegistryRecord::compare_offsets); - - for (i, record) in registry_records.into_iter().enumerate() { - let plugin = Plugin::deserialize( - &mut &(*ctx.accounts.asset.data).borrow()[record.offset..], - )?; - - let hashable_plugin_schema = HashablePluginSchema { - index: i, - authority: record.authority, - plugin, - }; - - let plugin_hash = hashable_plugin_schema.hash()?; - plugin_hashes.push(plugin_hash); - } - } - - let asset_hash = asset.hash()?; - let hashed_asset_schema = HashedAssetSchema { - asset_hash, - plugin_hashes, - }; - - let hashed_asset = HashedAsset::new(hashed_asset_schema.hash()?); - let serialized_data = hashed_asset.try_to_vec()?; + // Validate asset permissions. + let _ = validate_asset_permissions( + ctx.accounts.authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + Asset::check_compress, + Collection::check_compress, + PluginType::check_compress, + Asset::validate_compress, + Collection::validate_compress, + Plugin::validate_compress, + )?; - resize_or_reallocate_account( + // Compress the asset and plugin registry into account space. + let compression_proof = compress_into_account_space( + asset, + plugin_registry, ctx.accounts.asset, payer, ctx.accounts.system_program, - serialized_data.len(), )?; - sol_memcpy( - &mut ctx.accounts.asset.try_borrow_mut_data()?, - &serialized_data, - serialized_data.len(), - ); + // Send the spl-noop event for indexing the compressed asset. + compression_proof.wrap() } - Key::HashedAsset => return Err(MplCoreError::AlreadyCompressed.into()), - _ => return Err(MplCoreError::IncorrectAccount.into()), + Key::HashedAsset => Err(MplCoreError::AlreadyCompressed.into()), + _ => Err(MplCoreError::IncorrectAccount.into()), } - - Ok(()) } diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index 7ffa68cc..74ac9b4f 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -1,7 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke, program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, }; @@ -12,7 +12,7 @@ use crate::{ create_meta_idempotent, initialize_plugin, CheckResult, Plugin, PluginType, ValidationResult, }, - state::{Asset, Compressible, DataState, HashedAsset, Key, UpdateAuthority, COLLECT_AMOUNT}, + state::{Asset, DataState, Key, UpdateAuthority, COLLECT_AMOUNT}, utils::fetch_core_data, }; @@ -69,9 +69,8 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> P let serialized_data = match args.data_state { DataState::AccountState => serialized_data, DataState::LedgerState => { - new_asset.wrap()?; - let hashed_asset = HashedAsset::new(new_asset.hash()?); - hashed_asset.try_to_vec()? + msg!("Error: Minting compressed is not implemented"); + return Err(MplCoreError::NotImplemented.into()); } }; @@ -144,7 +143,7 @@ pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> P ctx.accounts .owner .unwrap_or(ctx.accounts.update_authority.unwrap_or(ctx.accounts.payer)), - &args, + None, &record.authority, )?; if result == ValidationResult::Rejected { diff --git a/programs/mpl-core/src/processor/decompress.rs b/programs/mpl-core/src/processor/decompress.rs index 9c5e6844..03ceb83e 100644 --- a/programs/mpl-core/src/processor/decompress.rs +++ b/programs/mpl-core/src/processor/decompress.rs @@ -1,19 +1,15 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy, - system_program, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, system_program}; use crate::{ error::MplCoreError, instruction::accounts::DecompressAccounts, - plugins::{ - create_meta_idempotent, initialize_plugin, CheckResult, Plugin, PluginType, - ValidationResult, + plugins::{Plugin, PluginType}, + state::{Asset, Collection, CompressionProof, Key}, + utils::{ + load_key, rebuild_account_state_from_proof_data, validate_asset_permissions, verify_proof, }, - state::{Asset, CompressionProof, Key}, - utils::{fetch_core_data, load_key, resize_or_reallocate_account, verify_proof}, }; #[repr(C)] @@ -30,12 +26,12 @@ pub(crate) fn decompress<'a>( let ctx = DecompressAccounts::context(accounts)?; // Guards. - assert_signer(ctx.accounts.owner)?; + assert_signer(ctx.accounts.authority)?; let payer = if let Some(payer) = ctx.accounts.payer { assert_signer(payer)?; payer } else { - ctx.accounts.owner + ctx.accounts.authority }; if *ctx.accounts.system_program.key != system_program::id() { @@ -44,82 +40,35 @@ pub(crate) fn decompress<'a>( match load_key(ctx.accounts.asset, 0)? { Key::HashedAsset => { + // Verify the proof and rebuild Asset struct in account space. let (asset, plugins) = verify_proof(ctx.accounts.asset, &args.compression_proof)?; - let serialized_data = asset.try_to_vec()?; - resize_or_reallocate_account( + // Use the data from the compression proof to rebuild the account. + rebuild_account_state_from_proof_data( + asset, + plugins, ctx.accounts.asset, payer, ctx.accounts.system_program, - serialized_data.len(), )?; - sol_memcpy( - &mut ctx.accounts.asset.try_borrow_mut_data()?, - &serialized_data, - serialized_data.len(), - ); - - if !plugins.is_empty() { - create_meta_idempotent(ctx.accounts.asset, payer, ctx.accounts.system_program)?; - - for plugin in plugins { - initialize_plugin::( - &plugin.plugin, - &plugin.authority, - ctx.accounts.asset, - payer, - ctx.accounts.system_program, - )?; - } - } - - let (asset, _, plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; - - let mut approved = false; - match Asset::check_decompress() { - CheckResult::CanApprove | CheckResult::CanReject => { - match asset.validate_decompress(&ctx.accounts)? { - ValidationResult::Approved => { - approved = true; - } - ValidationResult::Rejected => { - return Err(MplCoreError::InvalidAuthority.into()) - } - ValidationResult::Pass => (), - } - } - CheckResult::None => (), - }; - - if let Some(plugin_registry) = plugin_registry { - for record in plugin_registry.registry { - if matches!( - PluginType::check_decompress(&record.plugin_type), - CheckResult::CanApprove | CheckResult::CanReject - ) { - let result = Plugin::validate_decompress( - &Plugin::load(ctx.accounts.asset, record.offset)?, - ctx.accounts.owner, - &args, - &record.authority, - )?; - if result == ValidationResult::Rejected { - return Err(MplCoreError::InvalidAuthority.into()); - } else if result == ValidationResult::Approved { - approved = true; - } - } - } - }; + // Validate asset permissions. + let _ = validate_asset_permissions( + ctx.accounts.authority, + ctx.accounts.asset, + ctx.accounts.collection, + None, + Asset::check_decompress, + Collection::check_decompress, + PluginType::check_decompress, + Asset::validate_decompress, + Collection::validate_decompress, + Plugin::validate_decompress, + )?; - if !approved { - return Err(MplCoreError::InvalidAuthority.into()); - } + Ok(()) } - Key::Asset => return Err(MplCoreError::AlreadyDecompressed.into()), - _ => return Err(MplCoreError::IncorrectAccount.into()), + Key::Asset => Err(MplCoreError::AlreadyDecompressed.into()), + _ => Err(MplCoreError::IncorrectAccount.into()), } - - Ok(()) } diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index 594de83e..84c2b757 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -6,8 +6,11 @@ use crate::{ error::MplCoreError, instruction::accounts::TransferAccounts, plugins::{Plugin, PluginType}, - state::{Asset, Collection, Compressible, CompressionProof, HashedAsset, Key, SolanaAccount}, - utils::{load_key, validate_asset_permissions, verify_proof}, + state::{Asset, Collection, CompressionProof, Key, SolanaAccount, Wrappable}, + utils::{ + compress_into_account_space, load_key, rebuild_account_state_from_proof_data, + validate_asset_permissions, verify_proof, + }, }; #[repr(C)] @@ -22,31 +25,67 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) // Guards. assert_signer(ctx.accounts.authority)?; - if let Some(payer) = ctx.accounts.payer { + let payer = if let Some(payer) = ctx.accounts.payer { assert_signer(payer)?; - } + payer + } else { + ctx.accounts.authority + }; match load_key(ctx.accounts.asset, 0)? { Key::HashedAsset => { let compression_proof = args .compression_proof .ok_or(MplCoreError::MissingCompressionProof)?; - let (mut asset, _) = verify_proof(ctx.accounts.asset, &compression_proof)?; - if ctx.accounts.authority.key != &asset.owner { - return Err(MplCoreError::InvalidAuthority.into()); - } + let system_program = ctx + .accounts + .system_program + .ok_or(MplCoreError::MissingSystemProgram)?; - // TODO: Check delegates in compressed case. + // Verify the proof and rebuild Asset struct in account space. + let (mut asset, plugins) = verify_proof(ctx.accounts.asset, &compression_proof)?; + // Set the new owner. asset.owner = *ctx.accounts.new_owner.key; - asset.wrap()?; + // Use the data from the compression proof to rebuild the account. + rebuild_account_state_from_proof_data( + asset, + plugins, + ctx.accounts.asset, + payer, + system_program, + )?; + + // Validate asset permissions. + let (asset, _, plugin_registry) = validate_asset_permissions( + ctx.accounts.authority, + ctx.accounts.asset, + ctx.accounts.collection, + Some(ctx.accounts.new_owner), + Asset::check_transfer, + Collection::check_transfer, + PluginType::check_transfer, + Asset::validate_transfer, + Collection::validate_transfer, + Plugin::validate_transfer, + )?; + + // Compress the asset and plugin registry into account space. + let compression_proof = compress_into_account_space( + asset, + plugin_registry, + ctx.accounts.asset, + payer, + system_program, + )?; - // Make a new hashed asset with updated owner and save to account. - HashedAsset::new(asset.hash()?).save(ctx.accounts.asset, 0) + // Send the spl-noop event for indexing the compressed asset. + compression_proof.wrap() } Key::Asset => { + // Validate asset permissions. let (mut asset, _, _) = validate_asset_permissions( ctx.accounts.authority, ctx.accounts.asset, @@ -60,6 +99,7 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) Plugin::validate_transfer, )?; + // Set the new owner. asset.owner = *ctx.accounts.new_owner.key; asset.save(ctx.accounts.asset, 0) } diff --git a/programs/mpl-core/src/processor/update_plugin.rs b/programs/mpl-core/src/processor/update_plugin.rs index 5a52eca8..422a5a45 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -42,6 +42,7 @@ pub(crate) fn update_plugin<'a>( &Plugin::load(ctx.accounts.asset, registry_record.offset)?, &asset, ctx.accounts.authority, + None, ®istry_record.authority, )?; if result == ValidationResult::Rejected { @@ -90,6 +91,7 @@ pub(crate) fn update_collection_plugin<'a>( &Plugin::load(ctx.accounts.collection, registry_record.offset)?, &asset, ctx.accounts.authority, + None, ®istry_record.authority, )?; if result == ValidationResult::Rejected { diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index 40e7223f..54e3856f 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -3,7 +3,6 @@ use shank::ShankAccount; use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; use crate::{ - instruction::accounts::{CompressAccounts, DecompressAccounts}, plugins::{CheckResult, ValidationResult}, state::{Compressible, CompressionProof, DataBlob, Key, SolanaAccount}, }; @@ -90,9 +89,9 @@ impl Asset { /// Validate the compress lifecycle event. pub fn validate_compress( &self, - ctx: &CompressAccounts, + authority: &AccountInfo, ) -> Result { - if ctx.owner.key == &self.owner { + if authority.key == &self.owner { Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -102,9 +101,9 @@ impl Asset { /// Validate the decompress lifecycle event. pub fn validate_decompress( &self, - ctx: &DecompressAccounts, + authority: &AccountInfo, ) -> Result { - if ctx.owner.key == &self.owner { + if authority.key == &self.owner { Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index f674cba2..f29dc981 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -78,6 +78,14 @@ impl Collection { Ok(ValidationResult::Pass) } + /// Validate the burn lifecycle event. + pub fn validate_burn( + &self, + _authority: &AccountInfo, + ) -> Result { + Ok(ValidationResult::Pass) + } + /// Validate the update lifecycle event. pub fn validate_update( &self, @@ -90,13 +98,20 @@ impl Collection { } } - /// Validate the burn lifecycle event. - pub fn validate_burn(&self, authority: &AccountInfo) -> Result { - if authority.key == &self.update_authority { - Ok(ValidationResult::Approved) - } else { - Ok(ValidationResult::Pass) - } + /// Validate the compress lifecycle event. + pub fn validate_compress( + &self, + _authority: &AccountInfo, + ) -> Result { + Ok(ValidationResult::Pass) + } + + /// Validate the decompress lifecycle event. + pub fn validate_decompress( + &self, + _authority: &AccountInfo, + ) -> Result { + Ok(ValidationResult::Pass) } } diff --git a/programs/mpl-core/src/state/compression_proof.rs b/programs/mpl-core/src/state/compression_proof.rs new file mode 100644 index 00000000..a46de91c --- /dev/null +++ b/programs/mpl-core/src/state/compression_proof.rs @@ -0,0 +1,35 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::state::{Asset, HashablePluginSchema, UpdateAuthority, Wrappable}; + +/// A simple struct to store the compression proof of an asset. +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct CompressionProof { + /// The owner of the asset. + pub owner: Pubkey, //32 + /// The update authority of the asset. + pub update_authority: UpdateAuthority, //33 + /// The name of the asset. + pub name: String, //4 + /// The URI of the asset that points to the off-chain data. + pub uri: String, //4 + /// The plugins for the asset. + pub plugins: Vec, //4 +} + +impl CompressionProof { + /// Create a new `CompressionProof`. + pub fn new(asset: Asset, plugins: Vec) -> Self { + Self { + owner: asset.owner, + update_authority: asset.update_authority, + name: asset.name, + uri: asset.uri, + plugins, + } + } +} + +impl Wrappable for CompressionProof {} diff --git a/programs/mpl-core/src/state/hashable_plugin_schema.rs b/programs/mpl-core/src/state/hashable_plugin_schema.rs new file mode 100644 index 00000000..5b397a34 --- /dev/null +++ b/programs/mpl-core/src/state/hashable_plugin_schema.rs @@ -0,0 +1,30 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use std::cmp::Ordering; + +use crate::{ + plugins::Plugin, + state::{Authority, Compressible}, +}; + +/// A type that stores a plugin's authorities and deserialized data into a +/// schema that will be later hashed into a hashed asset. Also used in +/// `CompressionProof`. +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct HashablePluginSchema { + /// This is the order the plugins are stored in the account, allowing us + /// to keep track of their order in the hashing. + pub index: usize, + /// The authorities who have permission to utilize a plugin. + pub authority: Authority, + /// The deserialized plugin. + pub plugin: Plugin, +} + +impl HashablePluginSchema { + /// Associated function for sorting `RegistryRecords` by offset. + pub fn compare_indeces(a: &HashablePluginSchema, b: &HashablePluginSchema) -> Ordering { + a.index.cmp(&b.index) + } +} + +impl Compressible for HashablePluginSchema {} diff --git a/programs/mpl-core/src/state/hashed_asset_schema.rs b/programs/mpl-core/src/state/hashed_asset_schema.rs index ff80728a..8864e4c1 100644 --- a/programs/mpl-core/src/state/hashed_asset_schema.rs +++ b/programs/mpl-core/src/state/hashed_asset_schema.rs @@ -1,32 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use std::cmp::Ordering; -use crate::{ - plugins::Plugin, - state::{Authority, Compressible}, -}; - -/// A type that stores a plugin's authorities and deserialized data into a -/// schema that will be later hashed into a hashed asset. -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] -pub struct HashablePluginSchema { - /// This is the order the plugins are stored in the account, allowing us - /// to keep track of their order in the hashing. - pub index: usize, - /// The authorities who have permission to utilize a plugin. - pub authority: Authority, - /// The deserialized plugin. - pub plugin: Plugin, -} - -impl HashablePluginSchema { - /// Associated function for sorting `RegistryRecords` by offset. - pub fn compare_indeces(a: &HashablePluginSchema, b: &HashablePluginSchema) -> Ordering { - a.index.cmp(&b.index) - } -} - -impl Compressible for HashablePluginSchema {} +use crate::state::Compressible; /// The hashed asset schema is a schema that contains a hash of the asset and a vec of plugin hashes. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] diff --git a/programs/mpl-core/src/state/mod.rs b/programs/mpl-core/src/state/mod.rs index 1869a9a1..2bb56ce4 100644 --- a/programs/mpl-core/src/state/mod.rs +++ b/programs/mpl-core/src/state/mod.rs @@ -1,21 +1,27 @@ mod asset; pub use asset::*; -mod hashed_asset; -pub use hashed_asset::*; +mod collect; +pub(crate) use collect::*; + +mod collection; +pub use collection::*; + +mod compression_proof; +pub use compression_proof::*; + +mod hashable_plugin_schema; +pub use hashable_plugin_schema::*; mod hashed_asset_schema; pub use hashed_asset_schema::*; +mod hashed_asset; +pub use hashed_asset::*; + mod traits; pub use traits::*; -mod collection; -pub use collection::*; - -mod collect; -pub use collect::*; - mod update_authority; pub use update_authority::*; @@ -103,19 +109,3 @@ impl Key { 1 } } - -/// A simple struct to store the compression proof of an asset. -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct CompressionProof { - /// The owner of the asset. - pub owner: Pubkey, //32 - /// The update authority of the asset. - pub update_authority: UpdateAuthority, //33 - /// The name of the asset. - pub name: String, //4 - /// The URI of the asset that points to the off-chain data. - pub uri: String, //4 - /// The plugins for the asset. - pub plugins: Vec, //4 -} diff --git a/programs/mpl-core/src/state/traits.rs b/programs/mpl-core/src/state/traits.rs index 76fe8b4e..008b6c45 100644 --- a/programs/mpl-core/src/state/traits.rs +++ b/programs/mpl-core/src/state/traits.rs @@ -44,14 +44,17 @@ pub trait SolanaAccount: BorshSerialize + BorshDeserialize { } } -/// A trait for assets that can be compressed and hashed. +/// A trait for data that can be compressed. pub trait Compressible: BorshSerialize + BorshDeserialize { /// Get the hash of the compressed data. fn hash(&self) -> Result<[u8; 32], ProgramError> { let serialized_data = self.try_to_vec()?; Ok(keccak::hash(serialized_data.as_slice()).to_bytes()) } +} +/// A trait for data that can be wrapped by the spl-noop program. +pub trait Wrappable: BorshSerialize + BorshDeserialize { /// Write the data to ledger state by wrapping it in a noop instruction. fn wrap(&self) -> ProgramResult { let serialized_data = self.try_to_vec()?; diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 10d85645..597c8fc3 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -1,15 +1,17 @@ +use borsh::{BorshDeserialize, BorshSerialize}; use num_traits::{FromPrimitive, ToPrimitive}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_error::ProgramError, rent::Rent, system_instruction, sysvar::Sysvar, + program_error::ProgramError, program_memory::sol_memcpy, rent::Rent, system_instruction, + sysvar::Sysvar, }; use std::collections::BTreeMap; use crate::{ error::MplCoreError, plugins::{ - validate_plugin_checks, CheckResult, Plugin, PluginHeader, PluginRegistry, PluginType, - RegistryRecord, ValidationResult, + create_meta_idempotent, initialize_plugin, validate_plugin_checks, CheckResult, Plugin, + PluginHeader, PluginRegistry, PluginType, RegistryRecord, ValidationResult, }, state::{ Asset, Authority, Collection, Compressible, CompressionProof, CoreAsset, DataBlob, @@ -202,7 +204,7 @@ pub(crate) fn resize_or_reallocate_account<'a>( } #[allow(clippy::too_many_arguments)] -/// Validate asset permissions using asset, collection, and plugin lifecycle validations. +/// Validate asset permissions using lifecycle validations for asset, collection, and plugins. pub fn validate_asset_permissions<'a>( authority: &AccountInfo<'a>, asset: &AccountInfo<'a>, @@ -268,10 +270,6 @@ pub fn validate_asset_permissions<'a>( }; solana_program::msg!("approved: {:#?}", approved); - // let custom_args = |plugin: &Plugin, authority_info: &AccountInfo<'a>, authority: &Authority| { - // Plugin::validate_transfer(plugin, authority_info, new_owner, authority) - // }; - approved = validate_plugin_checks( Key::Collection, &checks, @@ -299,7 +297,7 @@ pub fn validate_asset_permissions<'a>( Ok((deserialized_asset, plugin_header, plugin_registry)) } -/// Validate collection permissions using collection and plugin lifecycle validations. +/// Validate collection permissions using lifecycle validations for collection and plugins. pub fn validate_collection_permissions<'a>( authority: &AccountInfo<'a>, collection: &AccountInfo<'a>, @@ -359,3 +357,90 @@ pub fn validate_collection_permissions<'a>( Ok((deserialized_collection, plugin_header, plugin_registry)) } + +/// Take an `Asset` and Vec of `HashablePluginSchema` and rebuild the asset in account space. +pub fn rebuild_account_state_from_proof_data<'a>( + asset: Asset, + plugins: Vec, + asset_info: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + let serialized_data = asset.try_to_vec()?; + resize_or_reallocate_account(asset_info, payer, system_program, serialized_data.len())?; + + sol_memcpy( + &mut asset_info.try_borrow_mut_data()?, + &serialized_data, + serialized_data.len(), + ); + + // Add the plugins. + if !plugins.is_empty() { + create_meta_idempotent(asset_info, payer, system_program)?; + + for plugin in plugins { + initialize_plugin::( + &plugin.plugin, + &plugin.authority, + asset_info, + payer, + system_program, + )?; + } + } + + Ok(()) +} + +/// Take `Asset` and `PluginRegistry` for a decompressed asset, and compress into account space. +pub fn compress_into_account_space<'a>( + asset: Asset, + plugin_registry: Option, + asset_info: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> Result { + let asset_hash = asset.hash()?; + let mut compression_proof = CompressionProof::new(asset, vec![]); + let mut plugin_hashes = vec![]; + if let Some(plugin_registry) = plugin_registry { + let mut registry_records = plugin_registry.registry; + + // It should already be sorted but we just want to make sure. + registry_records.sort_by(RegistryRecord::compare_offsets); + + for (i, record) in registry_records.into_iter().enumerate() { + let plugin = Plugin::deserialize(&mut &(*asset_info.data).borrow()[record.offset..])?; + + let hashable_plugin_schema = HashablePluginSchema { + index: i, + authority: record.authority, + plugin, + }; + + let plugin_hash = hashable_plugin_schema.hash()?; + plugin_hashes.push(plugin_hash); + + compression_proof.plugins.push(hashable_plugin_schema); + } + } + + let hashed_asset_schema = HashedAssetSchema { + asset_hash, + plugin_hashes, + }; + + let hashed_asset = HashedAsset::new(hashed_asset_schema.hash()?); + let serialized_data = hashed_asset.try_to_vec()?; + + resize_or_reallocate_account(asset_info, payer, system_program, serialized_data.len())?; + + sol_memcpy( + &mut asset_info.try_borrow_mut_data()?, + &serialized_data, + serialized_data.len(), + ); + + Ok(compression_proof) +}