diff --git a/Cargo.lock b/Cargo.lock index 2ec85e41..e0de8c48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2080,7 +2080,7 @@ dependencies = [ [[package]] name = "mpl-core" -version = "0.4.3" +version = "0.4.4" dependencies = [ "assert_matches", "base64 0.22.0", diff --git a/clients/js/bench/marketplace.ts b/clients/js/bench/marketplace.ts new file mode 100644 index 00000000..828e97bc --- /dev/null +++ b/clients/js/bench/marketplace.ts @@ -0,0 +1,489 @@ +import { generateSigner, TransactionBuilder } from "@metaplex-foundation/umi"; +import test from "ava"; +import { existsSync, readFileSync, writeFileSync } from "fs"; +import { addPluginV1, addressPluginAuthority, createCollectionV1, createPlugin, createV1, pluginAuthorityPair, PluginType, revokePluginAuthorityV1, transferV1, updatePluginV1 } from "../src"; +import { createUmi } from "./_setup"; + +test('list an asset', async (t) => { + // Given an Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const marketplace = generateSigner(umi); + + await createV1(umi, { + asset: assetAddress, + name: "Test", + uri: "www.test.com", + }).sendAndConfirm(umi); + + let builder = addPluginV1(umi, { + asset: assetAddress.publicKey, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: true }, + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + }); + + builder = builder.add(addPluginV1(umi, { + asset: assetAddress.publicKey, + plugin: createPlugin({ + type: "TransferDelegate", + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + })); + + const tx = await builder.sendAndConfirm(umi); + + const compute = Number((await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); + + const cuResult = { + name: `CU: ${t.title}`, + unit: "Compute Units", + value: compute, + } + + // Read the results array from output.json + let output = []; + if (existsSync("./output.json")) { + output = JSON.parse(readFileSync("./output.json", 'utf-8')); + } + + // Push the result to the array + output.push(cuResult); + // Write the array to output.json + writeFileSync("./output.json", JSON.stringify(output, null, 2)); + + t.pass(); +}); + +test('sell an asset', async (t) => { + // Given an Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const newOwner = generateSigner(umi); + const marketplace = generateSigner(umi); + + await createV1(umi, { + asset: assetAddress, + name: "Test", + uri: "www.test.com", + }).sendAndConfirm(umi); + + let builder = addPluginV1(umi, { + asset: assetAddress.publicKey, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: true }, + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + }); + + builder = builder.add(addPluginV1(umi, { + asset: assetAddress.publicKey, + plugin: createPlugin({ + type: "TransferDelegate", + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + })); + + await builder.sendAndConfirm(umi); + + let sellBuilder = transferV1(umi, { + asset: assetAddress.publicKey, + newOwner: newOwner.publicKey, + payer: marketplace, + }); + + sellBuilder = updatePluginV1(umi, { + asset: assetAddress.publicKey, + payer: marketplace, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: false }, + }), + }); + + sellBuilder = sellBuilder.add(revokePluginAuthorityV1(umi, { + asset: assetAddress.publicKey, + payer: marketplace, + pluginType: PluginType.TransferDelegate, + })); + + sellBuilder = sellBuilder.add(revokePluginAuthorityV1(umi, { + asset: assetAddress.publicKey, + payer: marketplace, + pluginType: PluginType.FreezeDelegate, + })); + + const tx = await sellBuilder.sendAndConfirm(umi); + + const compute = Number((await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); + + const cuResult = { + name: `CU: ${t.title}`, + unit: "Compute Units", + value: compute, + } + + // Read the results array from output.json + let output = []; + if (existsSync("./output.json")) { + output = JSON.parse(readFileSync("./output.json", 'utf-8')); + } + + // Push the result to the array + output.push(cuResult); + // Write the array to output.json + writeFileSync("./output.json", JSON.stringify(output, null, 2)); + + t.pass(); +}); + +test('list an asset with empty collection', async (t) => { + // Given an Umi instance and a new signer. + const umi = await createUmi(); + const collectionAddress = generateSigner(umi); + const assetAddress = generateSigner(umi); + const marketplace = generateSigner(umi); + + let builder = new TransactionBuilder(); + builder = builder.add(createCollectionV1(umi, { + collection: collectionAddress, + name: "Test", + uri: "www.test.com", + })); + builder = builder.add(createV1(umi, { + asset: assetAddress, + collection: collectionAddress.publicKey, + name: "Test", + uri: "www.test.com", + })); + + await builder.sendAndConfirm(umi); + + let listBuilder = addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: true }, + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + }); + + listBuilder = listBuilder.add(addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "TransferDelegate", + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + })); + + const tx = await listBuilder.sendAndConfirm(umi); + + const compute = Number((await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); + + const cuResult = { + name: `CU: ${t.title}`, + unit: "Compute Units", + value: compute, + } + + // Read the results array from output.json + let output = []; + if (existsSync("./output.json")) { + output = JSON.parse(readFileSync("./output.json", 'utf-8')); + } + + // Push the result to the array + output.push(cuResult); + // Write the array to output.json + writeFileSync("./output.json", JSON.stringify(output, null, 2)); + + t.pass(); +}); + +test('sell an asset with empty collection', async (t) => { + // Given an Umi instance and a new signer. + const umi = await createUmi(); + const collectionAddress = generateSigner(umi); + const assetAddress = generateSigner(umi); + const newOwner = generateSigner(umi); + const marketplace = generateSigner(umi); + + let builder = new TransactionBuilder(); + builder = builder.add(createCollectionV1(umi, { + collection: collectionAddress, + name: "Test", + uri: "www.test.com", + })); + builder = builder.add(createV1(umi, { + asset: assetAddress, + collection: collectionAddress.publicKey, + name: "Test", + uri: "www.test.com", + })); + + await builder.sendAndConfirm(umi); + + let listBuilder = addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: true }, + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + }); + + listBuilder = listBuilder.add(addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "TransferDelegate", + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + })); + + await listBuilder.sendAndConfirm(umi); + + let sellBuilder = transferV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + newOwner: newOwner.publicKey, + payer: marketplace, + }); + + sellBuilder = updatePluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + payer: marketplace, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: false }, + }), + }); + + sellBuilder = sellBuilder.add(revokePluginAuthorityV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + payer: marketplace, + pluginType: PluginType.TransferDelegate, + })); + + sellBuilder = sellBuilder.add(revokePluginAuthorityV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + payer: marketplace, + pluginType: PluginType.FreezeDelegate, + })); + + const tx = await sellBuilder.sendAndConfirm(umi); + + const compute = Number((await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); + + const cuResult = { + name: `CU: ${t.title}`, + unit: "Compute Units", + value: compute, + } + + // Read the results array from output.json + let output = []; + if (existsSync("./output.json")) { + output = JSON.parse(readFileSync("./output.json", 'utf-8')); + } + + // Push the result to the array + output.push(cuResult); + // Write the array to output.json + writeFileSync("./output.json", JSON.stringify(output, null, 2)); + + t.pass(); +}); + +test('list an asset with collection royalties', async (t) => { + // Given an Umi instance and a new signer. + const umi = await createUmi(); + const collectionAddress = generateSigner(umi); + const assetAddress = generateSigner(umi); + const marketplace = generateSigner(umi); + + let builder = new TransactionBuilder(); + builder = builder.add(createCollectionV1(umi, { + collection: collectionAddress, + name: "Test", + uri: "www.test.com", + })); + builder = builder.add(createV1(umi, { + asset: assetAddress, + collection: collectionAddress.publicKey, + name: "Test", + uri: "www.test.com", + plugins: [pluginAuthorityPair({ + type: "Royalties", + data: { + basisPoints: 500, + creators: [{ address: umi.identity.publicKey, percentage: 100 }], + ruleSet: { + __kind: "None" + } + }, + })] + })); + + await builder.sendAndConfirm(umi); + + let listBuilder = addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: true }, + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + }); + + listBuilder = listBuilder.add(addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "TransferDelegate", + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + })); + + const tx = await listBuilder.sendAndConfirm(umi); + + const compute = Number((await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); + + const cuResult = { + name: `CU: ${t.title}`, + unit: "Compute Units", + value: compute, + } + + // Read the results array from output.json + let output = []; + if (existsSync("./output.json")) { + output = JSON.parse(readFileSync("./output.json", 'utf-8')); + } + + // Push the result to the array + output.push(cuResult); + // Write the array to output.json + writeFileSync("./output.json", JSON.stringify(output, null, 2)); + + t.pass(); +}); + +test('sell an asset with collection royalties', async (t) => { + // Given an Umi instance and a new signer. + const umi = await createUmi(); + const collectionAddress = generateSigner(umi); + const assetAddress = generateSigner(umi); + const newOwner = generateSigner(umi); + const marketplace = generateSigner(umi); + + let builder = new TransactionBuilder(); + builder = builder.add(createCollectionV1(umi, { + collection: collectionAddress, + name: "Test", + uri: "www.test.com", + })); + builder = builder.add(createV1(umi, { + asset: assetAddress, + collection: collectionAddress.publicKey, + name: "Test", + uri: "www.test.com", + plugins: [pluginAuthorityPair({ + type: "Royalties", + data: { + basisPoints: 500, + creators: [{ address: umi.identity.publicKey, percentage: 100 }], + ruleSet: { + __kind: "None" + } + }, + })] + })); + + await builder.sendAndConfirm(umi); + + let listBuilder = addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: true }, + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + }); + + listBuilder = listBuilder.add(addPluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + plugin: createPlugin({ + type: "TransferDelegate", + }), + initAuthority: addressPluginAuthority(marketplace.publicKey), + })); + + await listBuilder.sendAndConfirm(umi); + + let sellBuilder = transferV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + newOwner: newOwner.publicKey, + payer: marketplace, + }); + + sellBuilder = updatePluginV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + payer: marketplace, + plugin: createPlugin({ + type: "FreezeDelegate", + data: { frozen: false }, + }), + }); + + sellBuilder = sellBuilder.add(revokePluginAuthorityV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + payer: marketplace, + pluginType: PluginType.TransferDelegate, + })); + + sellBuilder = sellBuilder.add(revokePluginAuthorityV1(umi, { + asset: assetAddress.publicKey, + collection: collectionAddress.publicKey, + payer: marketplace, + pluginType: PluginType.FreezeDelegate, + })); + + const tx = await sellBuilder.sendAndConfirm(umi); + + const compute = Number((await umi.rpc.getTransaction(tx.signature))?.meta.computeUnitsConsumed); + + const cuResult = { + name: `CU: ${t.title}`, + unit: "Compute Units", + value: compute, + } + + // Read the results array from output.json + let output = []; + if (existsSync("./output.json")) { + output = JSON.parse(readFileSync("./output.json", 'utf-8')); + } + + // Push the result to the array + output.push(cuResult); + // Write the array to output.json + writeFileSync("./output.json", JSON.stringify(output, null, 2)); + + t.pass(); +}); \ No newline at end of file diff --git a/clients/js/bench/transfer.ts b/clients/js/bench/transfer.ts index 7950f524..248b0937 100644 --- a/clients/js/bench/transfer.ts +++ b/clients/js/bench/transfer.ts @@ -160,6 +160,7 @@ test('transfer an asset with plugins and empty collection', async (t) => { })); builder = builder.add(createV1(umi, { asset: assetAddress, + collection: collectionAddress.publicKey, name: "Test", uri: "www.test.com", plugins: [ diff --git a/clients/js/package.json b/clients/js/package.json index 1979e4f7..d44fe580 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -8,7 +8,7 @@ "build": "rimraf dist && tsc -p tsconfig.json", "build:docs": "typedoc", "test": "ava --timeout 600s", - "bench": "ava ./dist/bench/*.js", + "bench": "rm -f output.json && ava ./dist/bench/*.js --serial --no-worker-threads", "lint": "eslint --ext js,ts,tsx src", "lint:fix": "eslint --fix --ext js,ts,tsx src", "format": "prettier --check src test", diff --git a/clients/js/test/removePlugin.test.ts b/clients/js/test/removePlugin.test.ts index f73c8a20..24dd43aa 100644 --- a/clients/js/test/removePlugin.test.ts +++ b/clients/js/test/removePlugin.test.ts @@ -94,7 +94,7 @@ test('it can remove authority managed plugin from collection', async (t) => { t.is(collection2.updateDelegate, undefined); }); -test('it can remove authority managed plugin from asset using update auth', async (t) => { +test('it can remove authority managed plugin from asset in collection using update auth', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); const updateAuth = generateSigner(umi); @@ -137,6 +137,48 @@ test('it can remove authority managed plugin from asset using update auth', asyn t.is(asset2.royalties, undefined); }); +test('it can remove authority managed plugin from asset not in collection using update auth', async (t) => { + const umi = await createUmi(); + + const assetAuth = generateSigner(umi); + const asset = await createAsset(umi, { + updateAuthority: assetAuth, + plugins: [ + pluginAuthorityPair({ + type: 'Attributes', + data: { attributeList: [{ key: 'key', value: 'value' }] }, + }), + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'value' }], + }, + }); + + await removePluginV1(umi, { + asset: asset.publicKey, + pluginType: PluginType.Attributes, + authority: assetAuth, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: undefined, + }); +}); + test('it can remove authority managed plugin from collection using delegate auth', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); @@ -609,3 +651,106 @@ test('it cannot use an invalid noop program for collections', async (t) => { await t.throwsAsync(result, { name: 'InvalidLogWrapperProgram' }); }); + +test('it cannot remove an authority managed plugin from an asset if not the authority', async (t) => { + const umi = await createUmi(); + + const assetAuth = generateSigner(umi); + const asset = await createAsset(umi, { + updateAuthority: assetAuth, + plugins: [ + pluginAuthorityPair({ + type: 'Attributes', + data: { attributeList: [{ key: 'key', value: 'value' }] }, + }), + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'value' }], + }, + }); + + const result = removePluginV1(umi, { + asset: asset.publicKey, + pluginType: PluginType.Attributes, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'NoApprovals' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'value' }], + }, + }); +}); + +test('it cannot use an invalid collection to remove a plugin on an asset', async (t) => { + const umi = await createUmi(); + + const assetAuth = generateSigner(umi); + const asset = await createAsset(umi, { + updateAuthority: assetAuth, + plugins: [ + pluginAuthorityPair({ + type: 'Attributes', + data: { attributeList: [{ key: 'key', value: 'value' }] }, + }), + ], + }); + + const collectionAuth = generateSigner(umi); + const wrongCollection = await createCollection(umi, { + updateAuthority: collectionAuth.publicKey, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'value' }], + }, + }); + + const result = removePluginV1(umi, { + asset: asset.publicKey, + collection: wrongCollection.publicKey, + pluginType: PluginType.Attributes, + authority: collectionAuth, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidCollection' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'value' }], + }, + }); +}); diff --git a/clients/js/test/updatePlugin.test.ts b/clients/js/test/updatePlugin.test.ts index 653bd43c..7191964d 100644 --- a/clients/js/test/updatePlugin.test.ts +++ b/clients/js/test/updatePlugin.test.ts @@ -6,6 +6,7 @@ import { createPlugin, updateCollectionPluginV1, updatePluginV1, + pluginAuthorityPair, } from '../src'; import { DEFAULT_ASSET, @@ -150,3 +151,63 @@ test('it cannot use an invalid noop program for collections', async (t) => { await t.throwsAsync(result, { name: 'InvalidLogWrapperProgram' }); }); + +test('it cannot use an invalid collection to update a plugin on an asset', async (t) => { + const umi = await createUmi(); + + const assetAuth = generateSigner(umi); + const asset = await createAsset(umi, { + updateAuthority: assetAuth, + plugins: [ + pluginAuthorityPair({ + type: 'Attributes', + data: { attributeList: [{ key: 'key', value: 'initial' }] }, + }), + ], + }); + + const collectionAuth = generateSigner(umi); + const wrongCollection = await createCollection(umi, { + updateAuthority: collectionAuth.publicKey, + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'initial' }], + }, + }); + + const result = updatePluginV1(umi, { + asset: asset.publicKey, + collection: wrongCollection.publicKey, + plugin: createPlugin({ + type: 'Attributes', + data: { + attributeList: [{ key: 'key', value: 'updated' }], + }, + }), + authority: collectionAuth, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidCollection' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: assetAuth.publicKey }, + attributes: { + authority: { + type: 'UpdateAuthority', + }, + attributeList: [{ key: 'key', value: 'initial' }], + }, + }); +}); diff --git a/clients/rust/Cargo.toml b/clients/rust/Cargo.toml index 3b80ed3b..b802a0a1 100644 --- a/clients/rust/Cargo.toml +++ b/clients/rust/Cargo.toml @@ -2,7 +2,7 @@ name = "mpl-core" description = "A flexible digital asset standard for Solana" repository = "https://github.com/metaplex-foundation/mpl-core" -version = "0.4.3" +version = "0.4.4" edition = "2021" readme = "README.md" license-file = "../../LICENSE" diff --git a/clients/rust/src/generated/types/data_state.rs b/clients/rust/src/generated/types/data_state.rs index 2c913cd7..c3f910a7 100644 --- a/clients/rust/src/generated/types/data_state.rs +++ b/clients/rust/src/generated/types/data_state.rs @@ -7,8 +7,11 @@ use borsh::BorshDeserialize; use borsh::BorshSerialize; +use num_derive::FromPrimitive; -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive( + BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive, +)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DataState { AccountState, diff --git a/clients/rust/src/generated/types/key.rs b/clients/rust/src/generated/types/key.rs index 411e370a..451a6393 100644 --- a/clients/rust/src/generated/types/key.rs +++ b/clients/rust/src/generated/types/key.rs @@ -7,8 +7,11 @@ use borsh::BorshDeserialize; use borsh::BorshSerialize; +use num_derive::FromPrimitive; -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive( + BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive, +)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Key { Uninitialized, diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index e77335a8..58640a04 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -7,8 +7,11 @@ use borsh::BorshDeserialize; use borsh::BorshSerialize; +use num_derive::FromPrimitive; -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive( + BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, FromPrimitive, +)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PluginType { Royalties, diff --git a/clients/rust/src/hooked/mod.rs b/clients/rust/src/hooked/mod.rs index f76fa042..860e42a6 100644 --- a/clients/rust/src/hooked/mod.rs +++ b/clients/rust/src/hooked/mod.rs @@ -11,6 +11,7 @@ pub mod collection; pub use collection::*; use borsh::{BorshDeserialize, BorshSerialize}; +use num_traits::FromPrimitive; use std::{cmp::Ordering, mem::size_of}; use crate::{ @@ -20,25 +21,6 @@ use crate::{ }; use solana_program::account_info::AccountInfo; -impl PluginType { - // Needed to determine if a plugin is a known or unknown type. - pub fn from_u8(n: u8) -> Option { - match n { - 0 => Some(PluginType::Royalties), - 1 => Some(PluginType::FreezeDelegate), - 2 => Some(PluginType::BurnDelegate), - 3 => Some(PluginType::TransferDelegate), - 4 => Some(PluginType::UpdateDelegate), - 5 => Some(PluginType::PermanentFreezeDelegate), - 6 => Some(PluginType::Attributes), - 7 => Some(PluginType::PermanentTransferDelegate), - 8 => Some(PluginType::PermanentBurnDelegate), - 9 => Some(PluginType::Edition), - _ => None, - } - } -} - impl From<&Plugin> for PluginType { fn from(plugin: &Plugin) -> Self { match plugin { @@ -114,20 +96,6 @@ impl SolanaAccount for PluginHeaderV1 { } } -impl Key { - pub fn from_u8(value: u8) -> Option { - match value { - 0 => Some(Key::Uninitialized), - 1 => Some(Key::AssetV1), - 2 => Some(Key::HashedAssetV1), - 3 => Some(Key::PluginHeaderV1), - 4 => Some(Key::PluginRegistryV1), - 5 => Some(Key::CollectionV1), - _ => None, - } - } -} - /// Load the one byte key from the account data at the given offset. pub fn load_key(account: &AccountInfo, offset: usize) -> Result { let key = Key::from_u8((*account.data).borrow()[offset]).ok_or(std::io::Error::new( diff --git a/clients/rust/src/hooked/plugin.rs b/clients/rust/src/hooked/plugin.rs index 713c11c7..8f7ae261 100644 --- a/clients/rust/src/hooked/plugin.rs +++ b/clients/rust/src/hooked/plugin.rs @@ -1,4 +1,5 @@ use borsh::BorshDeserialize; +use num_traits::FromPrimitive; use solana_program::account_info::AccountInfo; use crate::{ diff --git a/clients/rust/src/indexable_asset.rs b/clients/rust/src/indexable_asset.rs index b458aa23..bd3c0147 100644 --- a/clients/rust/src/indexable_asset.rs +++ b/clients/rust/src/indexable_asset.rs @@ -1,5 +1,6 @@ use base64::prelude::*; use borsh::BorshDeserialize; +use num_traits::FromPrimitive; use solana_program::pubkey::Pubkey; use std::{collections::HashMap, io::ErrorKind}; diff --git a/package.json b/package.json index 83512b6c..ecfe3e28 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ }, "devDependencies": { "@metaplex-foundation/amman": "^0.12.1", - "@metaplex-foundation/kinobi": "^0.17.7", + "@metaplex-foundation/kinobi": "^0.18.3", "@metaplex-foundation/shank-js": "^0.1.7", "typescript": "^4.9.4" }, "packageManager": "pnpm@8.9.0" -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93c01b4d..19fba33a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ devDependencies: specifier: ^0.12.1 version: 0.12.1(typescript@4.9.4) '@metaplex-foundation/kinobi': - specifier: ^0.17.7 - version: 0.17.7(fastestsmallesttextencoderdecoder@1.0.22) + specifier: ^0.18.3 + version: 0.18.3(fastestsmallesttextencoderdecoder@1.0.22) '@metaplex-foundation/shank-js': specifier: ^0.1.7 version: 0.1.7 @@ -89,15 +89,16 @@ packages: resolution: {integrity: sha512-S9RulC2fFCFOQraz61bij+5YCHhSO9llJegK8c8Y6731fSi6snUSQJdCUqYS8AIgR0TKbQvdvgSyIIdbDFZbBA==} dev: true - /@metaplex-foundation/kinobi@0.17.7(fastestsmallesttextencoderdecoder@1.0.22): - resolution: {integrity: sha512-cfx33zB/a0WH4q9ECoIIEGY3xmQZnmCQThz0oEqUZQ3755DIAv+WwHz3smTvKCLgNj2zchZ45O0xggo/egf1sA==} + /@metaplex-foundation/kinobi@0.18.3(fastestsmallesttextencoderdecoder@1.0.22): + resolution: {integrity: sha512-va8n1DVYlzhVA6xLpTgiUzxRUB3Dvo53pwDUYYEuKBX43zXKrgg5SWZ438UX23/5iFNv+6M01L+5/Z4oh43fAw==} dependencies: '@noble/hashes': 1.4.0 - '@solana/codecs-strings': 2.0.0-experimental.a157265(fastestsmallesttextencoderdecoder@1.0.22) + '@prettier/sync': 0.5.1(prettier@3.2.5) + '@solana/codecs-strings': 2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22) chalk: 4.1.2 json-stable-stringify: 1.1.1 nunjucks: 3.2.4 - prettier: 2.8.8 + prettier: 3.2.5 transitivePeerDependencies: - chokidar - fastestsmallesttextencoderdecoder @@ -135,6 +136,15 @@ packages: engines: {node: '>= 16'} dev: true + /@prettier/sync@0.5.1(prettier@3.2.5): + resolution: {integrity: sha512-tpF+A1e4ynO2U4fTH21Sjgm9EYENmqg4zmJCMLrmLVfzIzuDc1cKGXyxrxbFgcH8qQRfowyDCZFAUukwhiZlsw==} + peerDependencies: + prettier: '*' + dependencies: + make-synchronized: 0.2.8 + prettier: 3.2.5 + dev: true + /@sideway/address@4.1.5: resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} dependencies: @@ -174,26 +184,38 @@ packages: buffer: 6.0.3 dev: true - /@solana/codecs-core@2.0.0-experimental.a157265: - resolution: {integrity: sha512-78SjZiLWWjAn5sVL92T5W+4LXYG01Vu28tVbtiCWi4QEX21794bW6lMcXbPmbivFPbD8Dje+EHAYADfxYj6RTA==} + /@solana/codecs-core@2.0.0-preview.1: + resolution: {integrity: sha512-0y3kgFSJAOApNGefEOgLRMiXOHVmcF29Xw3zmR4LDUABvytG3ecKMXGz5oLt3vdaU2CyN3tuUVeGmQjrd0U1kw==} + dependencies: + '@solana/errors': 2.0.0-preview.1 dev: true - /@solana/codecs-numbers@2.0.0-experimental.a157265: - resolution: {integrity: sha512-Bkp7Y1KyhMsu8tHKxwHDXCnYxEawT0P4kEsIO1Fzs54pxOS1jtwJHo7e4FE8Kv8ZSajUQAnbmQqj5GaIU08h9A==} + /@solana/codecs-numbers@2.0.0-preview.1: + resolution: {integrity: sha512-NFA8itgcYUY3hkWMBpVRozd2poy1zfOvkIWZKx/D69oIMUtQTBpKrodRVBuhlBkAv12vDNkFljqVySpcMZMl7A==} dependencies: - '@solana/codecs-core': 2.0.0-experimental.a157265 + '@solana/codecs-core': 2.0.0-preview.1 + '@solana/errors': 2.0.0-preview.1 dev: true - /@solana/codecs-strings@2.0.0-experimental.a157265(fastestsmallesttextencoderdecoder@1.0.22): - resolution: {integrity: sha512-1AypxqrFipI053/EOHZnDDcfKBduLjJdrpkbcqrgWJyciv/7+fMQjkgjbG+C8Vcqx5G8weUesXCcvAhqbdRwMw==} + /@solana/codecs-strings@2.0.0-preview.1(fastestsmallesttextencoderdecoder@1.0.22): + resolution: {integrity: sha512-kBAxE9ZD5/c8j9CkPxqc55dbo7R50Re3k94SXR+k13DZCCs37Fyn0/mAkw/S95fofbi/zsi4jSfmsT5vCZD75A==} peerDependencies: fastestsmallesttextencoderdecoder: ^1.0.22 dependencies: - '@solana/codecs-core': 2.0.0-experimental.a157265 - '@solana/codecs-numbers': 2.0.0-experimental.a157265 + '@solana/codecs-core': 2.0.0-preview.1 + '@solana/codecs-numbers': 2.0.0-preview.1 + '@solana/errors': 2.0.0-preview.1 fastestsmallesttextencoderdecoder: 1.0.22 dev: true + /@solana/errors@2.0.0-preview.1: + resolution: {integrity: sha512-mnBWfLVwMH4hxwb4sWZ7G7djQCMsyymjysvUPDiF89LueTBm1hfdxUv8Cy1uUCGsJ3jO3scdPwB4noOgr0rG/g==} + hasBin: true + dependencies: + chalk: 5.3.0 + commander: 12.0.0 + dev: true + /@solana/spl-token-registry@0.2.4574: resolution: {integrity: sha512-JzlfZmke8Rxug20VT/VpI2XsXlsqMlcORIUivF+Yucj7tFi7A0dXG7h+2UnD0WaZJw8BrUz2ABNkUnv89vbv1A==} engines: {node: '>=10'} @@ -438,6 +460,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /check-more-types@2.24.0: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} @@ -470,6 +497,11 @@ packages: delayed-stream: 1.0.0 dev: true + /commander@12.0.0: + resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} + engines: {node: '>=18'} + dev: true + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true @@ -876,6 +908,10 @@ packages: yallist: 4.0.0 dev: true + /make-synchronized@0.2.8: + resolution: {integrity: sha512-jtXnKYCxjmGaXiZhXbDbGPbh4YyTvIIbOgcQjtAboc4RSm9k3nyhTFvFQB0cfs7QFKuZXKe2D2RvOkv1c+vpxg==} + dev: true + /map-stream@0.1.0: resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} dev: true @@ -943,6 +979,7 @@ packages: /node-gyp-build@4.8.0: resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==} hasBin: true + requiresBuild: true dev: true /npm-run-path@4.0.1: @@ -1007,9 +1044,9 @@ packages: selective-whitespace: 1.0.4 dev: true - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} hasBin: true dev: true diff --git a/programs/mpl-core/src/plugins/burn_delegate.rs b/programs/mpl-core/src/plugins/burn_delegate.rs index a169550a..67f3226e 100644 --- a/programs/mpl-core/src/plugins/burn_delegate.rs +++ b/programs/mpl-core/src/plugins/burn_delegate.rs @@ -1,12 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; use crate::{ plugins::PluginType, state::{Authority, DataBlob}, }; -use super::{Plugin, PluginValidation, ValidationResult}; +use super::{PluginValidation, PluginValidationContext, ValidationResult}; /// This plugin manages additional permissions to burn. /// Any authorities approved are given permission to burn the asset on behalf of the owner. @@ -40,15 +40,14 @@ impl DataBlob for BurnDelegate { impl PluginValidation for BurnDelegate { fn validate_burn( &self, - authority_info: &AccountInfo, - authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - if authority + if ctx.self_authority == (&Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) { + solana_program::msg!("BurnDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -58,19 +57,16 @@ impl PluginValidation for BurnDelegate { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - solana_program::msg!("authority_info: {:?}", authority_info.key); - solana_program::msg!("authority: {:?}", authority); - if authority + if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) - && plugin_to_revoke.is_some() - && PluginType::from(plugin_to_revoke.unwrap()) == PluginType::BurnDelegate + && ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::BurnDelegate { + solana_program::msg!("BurnDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/edition.rs b/programs/mpl-core/src/plugins/edition.rs index c60334b2..1b1cde71 100644 --- a/programs/mpl-core/src/plugins/edition.rs +++ b/programs/mpl-core/src/plugins/edition.rs @@ -1,9 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; use crate::state::Authority; -use super::{Plugin, PluginType, PluginValidation, ValidationResult}; +use super::{PluginType, PluginValidation, PluginValidationContext, ValidationResult}; /// The edition plugin allows the creator to set an edition number on the asset /// The default authority for this plugin is the creator. @@ -24,13 +24,14 @@ impl Edition { impl PluginValidation for Edition { fn validate_add_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - new_plugin: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { // This plugin can only be added at creation time, so we // always reject it. - if new_plugin.is_some() && PluginType::from(new_plugin.unwrap()) == PluginType::Edition { + if ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::Edition + { + solana_program::msg!("Edition: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -39,13 +40,14 @@ impl PluginValidation for Edition { fn validate_remove_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - plugin: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { // This plugin cannot be removed // always reject it. - if plugin.is_some() && PluginType::from(plugin.unwrap()) == PluginType::Edition { + if ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::Edition + { + solana_program::msg!("Edition: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -54,17 +56,16 @@ impl PluginValidation for Edition { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if authority + if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) - && plugin_to_revoke.is_some() - && PluginType::from(plugin_to_revoke.unwrap()) == PluginType::Edition + && ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::Edition { + solana_program::msg!("Edition: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/freeze_delegate.rs b/programs/mpl-core/src/plugins/freeze_delegate.rs index bba5b0b4..ba6314f5 100644 --- a/programs/mpl-core/src/plugins/freeze_delegate.rs +++ b/programs/mpl-core/src/plugins/freeze_delegate.rs @@ -1,9 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; use crate::state::{Authority, DataBlob}; -use super::{Plugin, PluginValidation, ValidationResult}; +use super::{Plugin, PluginValidation, PluginValidationContext, ValidationResult}; /// The freeze delegate plugin allows any authority to lock the asset so it's no longer transferable. /// The default authority for this plugin is the owner. @@ -40,11 +40,10 @@ impl DataBlob for FreezeDelegate { impl PluginValidation for FreezeDelegate { fn validate_burn( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + _ctx: &PluginValidationContext, ) -> Result { if self.frozen { + solana_program::msg!("FreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -53,12 +52,10 @@ impl PluginValidation for FreezeDelegate { fn validate_transfer( &self, - _authority_info: &AccountInfo, - _new_owner: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + _ctx: &PluginValidationContext, ) -> Result { if self.frozen { + solana_program::msg!("FreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -67,12 +64,11 @@ impl PluginValidation for FreezeDelegate { fn validate_approve_plugin_authority( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - plugin_to_approve: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if let Some(Plugin::FreezeDelegate(freeze)) = plugin_to_approve { + if let Some(Plugin::FreezeDelegate(freeze)) = ctx.target_plugin { if freeze.frozen { + solana_program::msg!("FreezeDelegate: Rejected"); return Ok(ValidationResult::Rejected); } } @@ -82,18 +78,18 @@ impl PluginValidation for FreezeDelegate { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if let Some(Plugin::FreezeDelegate(freeze)) = plugin_to_revoke { + if let Some(Plugin::FreezeDelegate(freeze)) = ctx.target_plugin { if freeze.frozen { + solana_program::msg!("FreezeDelegate: Rejected"); return Ok(ValidationResult::Rejected); - } else if authority + } else if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) { + solana_program::msg!("FreezeDelegate: Approved"); return Ok(ValidationResult::Approved); } } @@ -104,11 +100,10 @@ impl PluginValidation for FreezeDelegate { /// Validate the remove plugin lifecycle action. fn validate_remove_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if plugin_to_revoke.is_some() && self.frozen { + if ctx.target_plugin.is_some() && self.frozen { + solana_program::msg!("FreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index c2d886a2..0db1c6c6 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -171,103 +171,64 @@ impl PluginType { impl Plugin { /// Validate the add plugin lifecycle event. - pub fn validate_add_plugin( + pub(crate) fn validate_add_plugin( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - new_plugin: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => { - royalties.validate_add_plugin(authority_info, authority, new_plugin) - } - Plugin::FreezeDelegate(freeze) => { - freeze.validate_add_plugin(authority_info, authority, new_plugin) - } - Plugin::BurnDelegate(burn) => { - burn.validate_add_plugin(authority_info, authority, new_plugin) - } - Plugin::TransferDelegate(transfer) => { - transfer.validate_add_plugin(authority_info, authority, new_plugin) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_add_plugin(authority_info, authority, new_plugin) - } + Plugin::Royalties(royalties) => royalties.validate_add_plugin(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_add_plugin(ctx), + Plugin::BurnDelegate(burn) => burn.validate_add_plugin(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_add_plugin(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_add_plugin(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_add_plugin(authority_info, authority, new_plugin) - } - Plugin::Attributes(attributes) => { - attributes.validate_add_plugin(authority_info, authority, new_plugin) + permanent_freeze.validate_add_plugin(ctx) } + Plugin::Attributes(attributes) => attributes.validate_add_plugin(ctx), Plugin::PermanentTransferDelegate(permanent_transfer) => { - permanent_transfer.validate_add_plugin(authority_info, authority, new_plugin) + permanent_transfer.validate_add_plugin(ctx) } Plugin::PermanentBurnDelegate(permanent_burn) => { - permanent_burn.validate_add_plugin(authority_info, authority, new_plugin) - } - Plugin::Edition(edition) => { - edition.validate_add_plugin(authority_info, authority, new_plugin) + permanent_burn.validate_add_plugin(ctx) } + Plugin::Edition(edition) => edition.validate_add_plugin(ctx), } } /// Validate the remove plugin lifecycle event. - pub fn validate_remove_plugin( + pub(crate) fn validate_remove_plugin( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - plugin_to_remove: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => { - royalties.validate_remove_plugin(authority_info, authority, plugin_to_remove) - } - Plugin::FreezeDelegate(freeze) => { - freeze.validate_remove_plugin(authority_info, authority, plugin_to_remove) - } - Plugin::BurnDelegate(burn) => { - burn.validate_remove_plugin(authority_info, authority, plugin_to_remove) - } - Plugin::TransferDelegate(transfer) => { - transfer.validate_remove_plugin(authority_info, authority, plugin_to_remove) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_remove_plugin(authority_info, authority, plugin_to_remove) - } + Plugin::Royalties(royalties) => royalties.validate_remove_plugin(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_remove_plugin(ctx), + Plugin::BurnDelegate(burn) => burn.validate_remove_plugin(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_remove_plugin(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_remove_plugin(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_remove_plugin(authority_info, authority, plugin_to_remove) + permanent_freeze.validate_remove_plugin(ctx) } - Plugin::Attributes(attributes) => { - attributes.validate_remove_plugin(authority_info, authority, plugin_to_remove) + Plugin::Attributes(attributes) => attributes.validate_remove_plugin(ctx), + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_remove_plugin(ctx) } - Plugin::PermanentTransferDelegate(permanent_transfer) => permanent_transfer - .validate_remove_plugin(authority_info, authority, plugin_to_remove), Plugin::PermanentBurnDelegate(permanent_burn) => { - permanent_burn.validate_remove_plugin(authority_info, authority, plugin_to_remove) - } - Plugin::Edition(edition) => { - edition.validate_remove_plugin(authority_info, authority, plugin_to_remove) + permanent_burn.validate_remove_plugin(ctx) } + Plugin::Edition(edition) => edition.validate_remove_plugin(ctx), } } /// Validate the approve plugin authority lifecycle event. - pub fn validate_approve_plugin_authority( + pub(crate) fn validate_approve_plugin_authority( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - plugin_to_approve: Option<&Plugin>, - _resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { // Universally, we cannot delegate a plugin authority if it's already delegated, even if // we're the manager. - if let Some(plugin_to_approve) = plugin_to_approve { - if plugin_to_approve == plugin && &plugin_to_approve.manager() != authority { + if let Some(plugin_to_approve) = ctx.target_plugin { + if plugin_to_approve == plugin && &plugin_to_approve.manager() != ctx.self_authority { return Err(MplCoreError::CannotRedelegate.into()); } } else { @@ -275,159 +236,102 @@ impl Plugin { } match plugin { - Plugin::Royalties(royalties) => royalties.validate_approve_plugin_authority( - authority_info, - authority, - plugin_to_approve, - ), - Plugin::FreezeDelegate(freeze) => freeze.validate_approve_plugin_authority( - authority_info, - authority, - plugin_to_approve, - ), - Plugin::BurnDelegate(burn) => { - burn.validate_approve_plugin_authority(authority_info, authority, plugin_to_approve) - } - Plugin::TransferDelegate(transfer) => transfer.validate_approve_plugin_authority( - authority_info, - authority, - plugin_to_approve, - ), - Plugin::UpdateDelegate(update_delegate) => update_delegate - .validate_approve_plugin_authority(authority_info, authority, plugin_to_approve), - Plugin::PermanentFreezeDelegate(permanent_freeze) => permanent_freeze - .validate_approve_plugin_authority(authority_info, authority, plugin_to_approve), - Plugin::Attributes(attributes) => attributes.validate_approve_plugin_authority( - authority_info, - authority, - plugin_to_approve, - ), - Plugin::PermanentTransferDelegate(permanent_transfer) => permanent_transfer - .validate_approve_plugin_authority(authority_info, authority, plugin_to_approve), - Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn - .validate_approve_plugin_authority(authority_info, authority, plugin_to_approve), - Plugin::Edition(edition) => edition.validate_approve_plugin_authority( - authority_info, - authority, - plugin_to_approve, - ), + Plugin::Royalties(royalties) => royalties.validate_approve_plugin_authority(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_approve_plugin_authority(ctx), + Plugin::BurnDelegate(burn) => burn.validate_approve_plugin_authority(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_approve_plugin_authority(ctx), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_approve_plugin_authority(ctx) + } + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_approve_plugin_authority(ctx) + } + Plugin::Attributes(attributes) => attributes.validate_approve_plugin_authority(ctx), + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_approve_plugin_authority(ctx) + } + Plugin::PermanentBurnDelegate(permanent_burn) => { + permanent_burn.validate_approve_plugin_authority(ctx) + } + Plugin::Edition(edition) => edition.validate_approve_plugin_authority(ctx), } } /// Validate the revoke plugin authority lifecycle event. - pub fn validate_revoke_plugin_authority( + pub(crate) fn validate_revoke_plugin_authority( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - if authority == &Authority::None { + if ctx.self_authority == &Authority::None { + solana_program::msg!("Base: Rejected"); return Ok(ValidationResult::Rejected); } match plugin { - Plugin::Royalties(royalties) => royalties.validate_revoke_plugin_authority( - authority_info, - authority, - plugin_to_revoke, - ), - Plugin::FreezeDelegate(freeze) => { - freeze.validate_revoke_plugin_authority(authority_info, authority, plugin_to_revoke) - } - Plugin::BurnDelegate(burn) => { - burn.validate_revoke_plugin_authority(authority_info, authority, plugin_to_revoke) - } - Plugin::TransferDelegate(transfer) => transfer.validate_revoke_plugin_authority( - authority_info, - authority, - plugin_to_revoke, - ), - Plugin::UpdateDelegate(update_delegate) => update_delegate - .validate_revoke_plugin_authority(authority_info, authority, plugin_to_revoke), - Plugin::PermanentFreezeDelegate(permanent_freeze) => permanent_freeze - .validate_revoke_plugin_authority(authority_info, authority, plugin_to_revoke), - Plugin::Attributes(attributes) => attributes.validate_revoke_plugin_authority( - authority_info, - authority, - plugin_to_revoke, - ), - Plugin::PermanentTransferDelegate(permanent_transfer) => permanent_transfer - .validate_revoke_plugin_authority(authority_info, authority, plugin_to_revoke), - Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn - .validate_revoke_plugin_authority(authority_info, authority, plugin_to_revoke), - Plugin::Edition(edition) => edition.validate_revoke_plugin_authority( - authority_info, - authority, - plugin_to_revoke, - ), + Plugin::Royalties(royalties) => royalties.validate_revoke_plugin_authority(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_revoke_plugin_authority(ctx), + Plugin::BurnDelegate(burn) => burn.validate_revoke_plugin_authority(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_revoke_plugin_authority(ctx), + Plugin::UpdateDelegate(update_delegate) => { + update_delegate.validate_revoke_plugin_authority(ctx) + } + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_revoke_plugin_authority(ctx) + } + Plugin::Attributes(attributes) => attributes.validate_revoke_plugin_authority(ctx), + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_revoke_plugin_authority(ctx) + } + Plugin::PermanentBurnDelegate(permanent_burn) => { + permanent_burn.validate_revoke_plugin_authority(ctx) + } + Plugin::Edition(edition) => edition.validate_revoke_plugin_authority(ctx), } } /// Route the validation of the create action to the appropriate plugin. pub(crate) fn validate_create( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - _: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => royalties.validate_create(authority_info, authority), - Plugin::FreezeDelegate(freeze) => freeze.validate_create(authority_info, authority), - Plugin::BurnDelegate(burn) => burn.validate_create(authority_info, authority), - Plugin::TransferDelegate(transfer) => { - transfer.validate_create(authority_info, authority) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_create(authority_info, authority) - } + Plugin::Royalties(royalties) => royalties.validate_create(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_create(ctx), + Plugin::BurnDelegate(burn) => burn.validate_create(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_create(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_create(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_create(authority_info, authority) + permanent_freeze.validate_create(ctx) } - Plugin::Attributes(attributes) => attributes.validate_create(authority_info, authority), + Plugin::Attributes(attributes) => attributes.validate_create(ctx), Plugin::PermanentTransferDelegate(permanent_transfer) => { - permanent_transfer.validate_create(authority_info, authority) + permanent_transfer.validate_create(ctx) } - Plugin::PermanentBurnDelegate(permanent_burn) => { - permanent_burn.validate_create(authority_info, authority) - } - Plugin::Edition(edition) => edition.validate_create(authority_info, authority), + Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_create(ctx), + Plugin::Edition(edition) => edition.validate_create(ctx), } } /// Route the validation of the update action to the appropriate plugin. pub(crate) fn validate_update( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - _: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => royalties.validate_update(authority_info, authority), - Plugin::FreezeDelegate(freeze) => freeze.validate_update(authority_info, authority), - Plugin::BurnDelegate(burn) => burn.validate_update(authority_info, authority), - Plugin::TransferDelegate(transfer) => { - transfer.validate_update(authority_info, authority) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_update(authority_info, authority) - } + Plugin::Royalties(royalties) => royalties.validate_update(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_update(ctx), + Plugin::BurnDelegate(burn) => burn.validate_update(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_update(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_update(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_update(authority_info, authority) + permanent_freeze.validate_update(ctx) } - Plugin::Attributes(attributes) => attributes.validate_update(authority_info, authority), + Plugin::Attributes(attributes) => attributes.validate_update(ctx), Plugin::PermanentTransferDelegate(permanent_transfer) => { - permanent_transfer.validate_update(authority_info, authority) + permanent_transfer.validate_update(ctx) } - Plugin::PermanentBurnDelegate(permanent_burn) => { - permanent_burn.validate_update(authority_info, authority) - } - Plugin::Edition(edition) => edition.validate_update(authority_info, authority), + Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_update(ctx), + Plugin::Edition(edition) => edition.validate_update(ctx), } } @@ -435,90 +339,37 @@ impl Plugin { /// There is no check for updating a plugin because the plugin itself MUST validate the change. pub(crate) fn validate_update_plugin( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - plugin_to_update: Option<&Plugin>, - resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - solana_program::msg!("Validating update plugin"); - let resolved_authorities = resolved_authorities.ok_or(MplCoreError::InvalidAuthority)?; - let plugin_to_update = plugin_to_update.ok_or(MplCoreError::InvalidPlugin)?; - let base_result = if resolved_authorities.contains(authority) { + let resolved_authorities = ctx + .resolved_authorities + .ok_or(MplCoreError::InvalidAuthority)?; + let base_result = if resolved_authorities.contains(ctx.self_authority) { + solana_program::msg!("Base: Approved"); ValidationResult::Approved } else { ValidationResult::Pass }; - solana_program::msg!("Base result: {:?}", base_result); - let result = match plugin { - Plugin::Royalties(royalties) => royalties.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::FreezeDelegate(freeze) => freeze.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::BurnDelegate(burn) => burn.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::TransferDelegate(transfer) => transfer.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::PermanentFreezeDelegate(permanent_freeze) => permanent_freeze - .validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::Attributes(attributes) => attributes.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::PermanentTransferDelegate(permanent_transfer) => permanent_transfer - .validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), - Plugin::Edition(edition) => edition.validate_update_plugin( - authority_info, - authority, - resolved_authorities, - plugin_to_update, - ), + Plugin::Royalties(royalties) => royalties.validate_update_plugin(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_update_plugin(ctx), + Plugin::BurnDelegate(burn) => burn.validate_update_plugin(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_update_plugin(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_update_plugin(ctx), + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_update_plugin(ctx) + } + Plugin::Attributes(attributes) => attributes.validate_update_plugin(ctx), + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_update_plugin(ctx) + } + Plugin::PermanentBurnDelegate(permanent_burn) => { + permanent_burn.validate_update_plugin(ctx) + } + Plugin::Edition(edition) => edition.validate_update_plugin(ctx), }?; - solana_program::msg!("Result: {:?}", result); - match (&base_result, &result) { (ValidationResult::Approved, ValidationResult::Approved) => { Ok(ValidationResult::Approved) @@ -542,175 +393,94 @@ impl Plugin { /// Route the validation of the burn action to the appropriate plugin. pub(crate) fn validate_burn( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - _: Option<&Plugin>, - resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => { - royalties.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::FreezeDelegate(freeze) => { - freeze.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::BurnDelegate(burn) => { - burn.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::TransferDelegate(transfer) => { - transfer.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_burn(authority_info, authority, resolved_authorities) - } + Plugin::Royalties(royalties) => royalties.validate_burn(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_burn(ctx), + Plugin::BurnDelegate(burn) => burn.validate_burn(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_burn(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_burn(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::Attributes(attributes) => { - attributes.validate_burn(authority_info, authority, resolved_authorities) + permanent_freeze.validate_burn(ctx) } + Plugin::Attributes(attributes) => attributes.validate_burn(ctx), Plugin::PermanentTransferDelegate(permanent_transfer) => { - permanent_transfer.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::PermanentBurnDelegate(permanent_burn) => { - permanent_burn.validate_burn(authority_info, authority, resolved_authorities) - } - Plugin::Edition(edition) => { - edition.validate_burn(authority_info, authority, resolved_authorities) + permanent_transfer.validate_burn(ctx) } + Plugin::PermanentBurnDelegate(permanent_burn) => permanent_burn.validate_burn(ctx), + Plugin::Edition(edition) => edition.validate_burn(ctx), } } /// Route the validation of the transfer action to the appropriate plugin. pub(crate) fn validate_transfer( plugin: &Plugin, - authority_info: &AccountInfo, - new_owner: Option<&AccountInfo>, - authority: &Authority, - _: Option<&Plugin>, - resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - let new_owner = new_owner.ok_or(MplCoreError::MissingNewOwner)?; match plugin { - Plugin::Royalties(royalties) => royalties.validate_transfer( - authority_info, - new_owner, - authority, - resolved_authorities, - ), - Plugin::FreezeDelegate(freeze) => { - freeze.validate_transfer(authority_info, new_owner, authority, resolved_authorities) + Plugin::Royalties(royalties) => royalties.validate_transfer(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_transfer(ctx), + Plugin::BurnDelegate(burn) => burn.validate_transfer(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_transfer(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_transfer(ctx), + Plugin::PermanentFreezeDelegate(permanent_freeze) => { + permanent_freeze.validate_transfer(ctx) } - Plugin::BurnDelegate(burn) => { - burn.validate_transfer(authority_info, new_owner, authority, resolved_authorities) + Plugin::PermanentTransferDelegate(permanent_transfer) => { + permanent_transfer.validate_transfer(ctx) } - Plugin::TransferDelegate(transfer) => transfer.validate_transfer( - authority_info, - new_owner, - authority, - resolved_authorities, - ), - Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_transfer( - authority_info, - new_owner, - authority, - resolved_authorities, - ), - Plugin::PermanentFreezeDelegate(permanent_freeze) => permanent_freeze - .validate_transfer(authority_info, new_owner, authority, resolved_authorities), - Plugin::PermanentTransferDelegate(permanent_transfer) => permanent_transfer - .validate_transfer(authority_info, new_owner, authority, resolved_authorities), - Plugin::Attributes(attributes_transfer) => attributes_transfer.validate_transfer( - authority_info, - new_owner, - authority, - resolved_authorities, - ), - Plugin::PermanentBurnDelegate(burn_transfer) => burn_transfer.validate_transfer( - authority_info, - new_owner, - authority, - resolved_authorities, - ), - Plugin::Edition(edition) => edition.validate_transfer( - authority_info, - new_owner, - authority, - resolved_authorities, - ), + Plugin::Attributes(attributes_transfer) => attributes_transfer.validate_transfer(ctx), + Plugin::PermanentBurnDelegate(burn_transfer) => burn_transfer.validate_transfer(ctx), + Plugin::Edition(edition) => edition.validate_transfer(ctx), } } /// Route the validation of the compress action to the appropriate plugin. pub(crate) fn validate_compress( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - _: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => royalties.validate_compress(authority_info, authority), - Plugin::FreezeDelegate(freeze) => freeze.validate_compress(authority_info, authority), - Plugin::BurnDelegate(burn) => burn.validate_compress(authority_info, authority), - Plugin::TransferDelegate(transfer) => { - transfer.validate_compress(authority_info, authority) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_compress(authority_info, authority) - } + Plugin::Royalties(royalties) => royalties.validate_compress(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_compress(ctx), + Plugin::BurnDelegate(burn) => burn.validate_compress(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_compress(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_compress(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_compress(authority_info, authority) - } - Plugin::Attributes(attributes) => { - attributes.validate_compress(authority_info, authority) + permanent_freeze.validate_compress(ctx) } + Plugin::Attributes(attributes) => attributes.validate_compress(ctx), Plugin::PermanentTransferDelegate(permanent_transfer) => { - permanent_transfer.validate_compress(authority_info, authority) - } - Plugin::PermanentBurnDelegate(burn_transfer) => { - burn_transfer.validate_compress(authority_info, authority) + permanent_transfer.validate_compress(ctx) } - Plugin::Edition(edition) => edition.validate_compress(authority_info, authority), + Plugin::PermanentBurnDelegate(burn_transfer) => burn_transfer.validate_compress(ctx), + Plugin::Edition(edition) => edition.validate_compress(ctx), } } /// Route the validation of the decompress action to the appropriate plugin. pub(crate) fn validate_decompress( plugin: &Plugin, - authority_info: &AccountInfo, - _: Option<&AccountInfo>, - authority: &Authority, - _: Option<&Plugin>, - _: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { match plugin { - Plugin::Royalties(royalties) => { - royalties.validate_decompress(authority_info, authority) - } - Plugin::FreezeDelegate(freeze) => freeze.validate_decompress(authority_info, authority), - Plugin::BurnDelegate(burn) => burn.validate_decompress(authority_info, authority), - Plugin::TransferDelegate(transfer) => { - transfer.validate_decompress(authority_info, authority) - } - Plugin::UpdateDelegate(update_delegate) => { - update_delegate.validate_decompress(authority_info, authority) - } + Plugin::Royalties(royalties) => royalties.validate_decompress(ctx), + Plugin::FreezeDelegate(freeze) => freeze.validate_decompress(ctx), + Plugin::BurnDelegate(burn) => burn.validate_decompress(ctx), + Plugin::TransferDelegate(transfer) => transfer.validate_decompress(ctx), + Plugin::UpdateDelegate(update_delegate) => update_delegate.validate_decompress(ctx), Plugin::PermanentFreezeDelegate(permanent_freeze) => { - permanent_freeze.validate_decompress(authority_info, authority) - } - Plugin::Attributes(attributes) => { - attributes.validate_decompress(authority_info, authority) + permanent_freeze.validate_decompress(ctx) } + Plugin::Attributes(attributes) => attributes.validate_decompress(ctx), Plugin::PermanentTransferDelegate(permanent_transfer) => { - permanent_transfer.validate_decompress(authority_info, authority) + permanent_transfer.validate_decompress(ctx) } Plugin::PermanentBurnDelegate(permanent_burn) => { - permanent_burn.validate_decompress(authority_info, authority) + permanent_burn.validate_decompress(ctx) } - Plugin::Edition(edition) => edition.validate_decompress(authority_info, authority), + Plugin::Edition(edition) => edition.validate_decompress(ctx), } } } @@ -737,11 +507,11 @@ pub(crate) struct PluginValidationContext<'a, 'b> { /// The authority account. pub authority_info: &'a AccountInfo<'a>, /// The resolved authority. - pub resolved_authorities: Option<&'a [Authority]>, + pub resolved_authorities: Option<&'b [Authority]>, /// The new owner account. pub new_owner: Option<&'a AccountInfo<'a>>, /// The new plugin. - pub target_plugin: Option<&'a Plugin>, + pub target_plugin: Option<&'b Plugin>, } /// Plugin validation trait which is implemented by each plugin. @@ -749,9 +519,7 @@ pub(crate) trait PluginValidation { /// Validate the add plugin lifecycle action. fn validate_add_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _new_plugin: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -759,9 +527,7 @@ pub(crate) trait PluginValidation { /// Validate the remove plugin lifecycle action. fn validate_remove_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _plugin_to_remove: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -769,9 +535,7 @@ pub(crate) trait PluginValidation { /// Validate the approve plugin authority lifecycle action. fn validate_approve_plugin_authority( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _plugin_to_approve: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -779,9 +543,7 @@ pub(crate) trait PluginValidation { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _plugin_to_revoke: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -789,8 +551,7 @@ pub(crate) trait PluginValidation { /// Validate the create lifecycle action. fn validate_create( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -798,8 +559,7 @@ pub(crate) trait PluginValidation { /// Validate the update lifecycle action. fn validate_update( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -807,10 +567,7 @@ pub(crate) trait PluginValidation { /// Validate the update_plugin lifecycle action. fn validate_update_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _resolved_authorities: &[Authority], - _plugin_to_update: &Plugin, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -818,9 +575,7 @@ pub(crate) trait PluginValidation { /// Validate the burn lifecycle action. fn validate_burn( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -828,10 +583,7 @@ pub(crate) trait PluginValidation { /// Validate the transfer lifecycle action. fn validate_transfer( &self, - _authority_info: &AccountInfo, - _new_owner: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -839,8 +591,7 @@ pub(crate) trait PluginValidation { /// Validate the compress lifecycle action. fn validate_compress( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -848,8 +599,7 @@ pub(crate) trait PluginValidation { /// Validate the decompress lifecycle action. fn validate_decompress( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -857,8 +607,7 @@ pub(crate) trait PluginValidation { /// Validate the add_authority lifecycle action. fn validate_add_authority( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -866,8 +615,7 @@ pub(crate) trait PluginValidation { /// Validate the add_authority lifecycle action. fn validate_remove_authority( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { Ok(ValidationResult::Pass) } @@ -880,20 +628,13 @@ pub(crate) trait PluginValidation { pub(crate) fn validate_plugin_checks<'a>( key: Key, checks: &BTreeMap, - authority: &AccountInfo<'a>, - new_owner: Option<&AccountInfo>, + authority: &'a AccountInfo<'a>, + new_owner: Option<&'a AccountInfo<'a>>, new_plugin: Option<&Plugin>, asset: Option<&AccountInfo<'a>>, collection: Option<&AccountInfo<'a>>, resolved_authorities: &[Authority], - validate_fp: fn( - &Plugin, - &AccountInfo<'a>, - Option<&AccountInfo>, - &Authority, - Option<&Plugin>, - Option<&[Authority]>, - ) -> Result, + validate_fp: fn(&Plugin, &PluginValidationContext) -> Result, ) -> Result { let mut approved = false; let mut rejected = false; @@ -904,21 +645,21 @@ pub(crate) fn validate_plugin_checks<'a>( CheckResult::CanApprove | CheckResult::CanReject ) { - solana_program::msg!("Validating plugin checks"); let account = match key { Key::CollectionV1 => collection.ok_or(MplCoreError::InvalidCollection)?, Key::AssetV1 => asset.ok_or(MplCoreError::InvalidAsset)?, _ => unreachable!(), }; - let result = validate_fp( - &Plugin::load(account, registry_record.offset)?, - authority, + let ctx = PluginValidationContext { + self_authority: ®istry_record.authority, + authority_info: authority, + resolved_authorities: Some(resolved_authorities), new_owner, - ®istry_record.authority, - new_plugin, - Some(resolved_authorities), - )?; + target_plugin: new_plugin, + }; + + let result = validate_fp(&Plugin::load(account, registry_record.offset)?, &ctx)?; match result { ValidationResult::Rejected => rejected = true, ValidationResult::Approved => approved = true, diff --git a/programs/mpl-core/src/plugins/permanent_burn_delegate.rs b/programs/mpl-core/src/plugins/permanent_burn_delegate.rs index 36c2efc3..5b3e06b7 100644 --- a/programs/mpl-core/src/plugins/permanent_burn_delegate.rs +++ b/programs/mpl-core/src/plugins/permanent_burn_delegate.rs @@ -1,9 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; -use crate::state::{Authority, DataBlob}; +use crate::state::DataBlob; -use super::{Plugin, PluginType, PluginValidation, ValidationResult}; +use super::{PluginType, PluginValidation, PluginValidationContext, ValidationResult}; /// The permanent burn plugin allows any authority to burn the asset. /// The default authority for this plugin is the update authority. @@ -24,15 +24,14 @@ impl DataBlob for PermanentBurnDelegate { impl PluginValidation for PermanentBurnDelegate { fn validate_add_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - new_plugin: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { // This plugin can only be added at creation time, so we // always reject it. - if new_plugin.is_some() - && PluginType::from(new_plugin.unwrap()) == PluginType::PermanentBurnDelegate + if ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::PermanentBurnDelegate { + solana_program::msg!("PermanentBurnDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -41,21 +40,19 @@ impl PluginValidation for PermanentBurnDelegate { fn validate_revoke_plugin_authority( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _plugin_to_revoke: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { + solana_program::msg!("PermanentBurnDelegate: Approved"); Ok(ValidationResult::Approved) } fn validate_burn( &self, - _authority_info: &AccountInfo, - authority: &Authority, - resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - if let Some(resolved_authorities) = resolved_authorities { - if resolved_authorities.contains(authority) { + if let Some(resolved_authorities) = ctx.resolved_authorities { + if resolved_authorities.contains(ctx.self_authority) { + solana_program::msg!("PermanentBurnDelegate: ForceApproved"); return Ok(ValidationResult::ForceApproved); } } diff --git a/programs/mpl-core/src/plugins/permanent_freeze_delegate.rs b/programs/mpl-core/src/plugins/permanent_freeze_delegate.rs index 2b8b6f8f..f96121c0 100644 --- a/programs/mpl-core/src/plugins/permanent_freeze_delegate.rs +++ b/programs/mpl-core/src/plugins/permanent_freeze_delegate.rs @@ -1,12 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; use crate::{ plugins::PluginType, state::{Authority, DataBlob}, }; -use super::{Plugin, PluginValidation, ValidationResult}; +use super::{PluginValidation, PluginValidationContext, ValidationResult}; /// The permanent freeze plugin allows any authority to lock the asset so it's no longer transferable. /// The default authority for this plugin is the update authority. @@ -43,11 +43,10 @@ impl DataBlob for PermanentFreezeDelegate { impl PluginValidation for PermanentFreezeDelegate { fn validate_burn( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + _ctx: &PluginValidationContext, ) -> Result { if self.frozen { + solana_program::msg!("PermanentFreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -56,12 +55,10 @@ impl PluginValidation for PermanentFreezeDelegate { fn validate_transfer( &self, - _authority_info: &AccountInfo, - _new_owner: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + _ctx: &PluginValidationContext, ) -> Result { if self.frozen { + solana_program::msg!("PermanentFreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -70,15 +67,14 @@ impl PluginValidation for PermanentFreezeDelegate { fn validate_add_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - new_plugin: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { // This plugin can only be added at creation time, so we // always reject it. - if new_plugin.is_some() - && PluginType::from(new_plugin.unwrap()) == PluginType::PermanentFreezeDelegate + if ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::PermanentFreezeDelegate { + solana_program::msg!("PermanentFreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -88,17 +84,16 @@ impl PluginValidation for PermanentFreezeDelegate { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if authority + if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) - && plugin_to_revoke.is_some() - && PluginType::from(plugin_to_revoke.unwrap()) == PluginType::PermanentFreezeDelegate + && ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::PermanentFreezeDelegate { + solana_program::msg!("PermanentFreezeDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -108,11 +103,10 @@ impl PluginValidation for PermanentFreezeDelegate { /// Validate the remove plugin lifecycle action. fn validate_remove_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if plugin_to_revoke.is_some() && self.frozen { + if ctx.target_plugin.is_some() && self.frozen { + solana_program::msg!("PermanentFreezeDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/permanent_transfer_delegate.rs b/programs/mpl-core/src/plugins/permanent_transfer_delegate.rs index 0ad72743..dc11b418 100644 --- a/programs/mpl-core/src/plugins/permanent_transfer_delegate.rs +++ b/programs/mpl-core/src/plugins/permanent_transfer_delegate.rs @@ -1,9 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; -use crate::state::{Authority, DataBlob}; +use crate::state::DataBlob; -use super::{Plugin, PluginType, PluginValidation, ValidationResult}; +use super::{PluginType, PluginValidation, PluginValidationContext, ValidationResult}; /// The permanent transfer plugin allows any authority to transfer the asset. /// The default authority for this plugin is the update authority. @@ -24,15 +24,14 @@ impl DataBlob for PermanentTransferDelegate { impl PluginValidation for PermanentTransferDelegate { fn validate_add_plugin( &self, - _authority: &AccountInfo, - _authorities: &Authority, - new_plugin: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { // This plugin can only be added at creation time, so we // always reject it. - if new_plugin.is_some() - && PluginType::from(new_plugin.unwrap()) == PluginType::PermanentTransferDelegate + if ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::PermanentTransferDelegate { + solana_program::msg!("PermanentTransferDelegate: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -41,22 +40,19 @@ impl PluginValidation for PermanentTransferDelegate { fn validate_revoke_plugin_authority( &self, - _authority: &AccountInfo, - _authorities: &Authority, - _plugin_to_revoke: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { + solana_program::msg!("PermanentTransferDelegate: Approved"); Ok(ValidationResult::Approved) } fn validate_transfer( &self, - _authority_info: &AccountInfo, - _new_owner: &AccountInfo, - authority: &Authority, - resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - if let Some(resolved_authorities) = resolved_authorities { - if resolved_authorities.contains(authority) { + if let Some(resolved_authorities) = ctx.resolved_authorities { + if resolved_authorities.contains(ctx.self_authority) { + solana_program::msg!("PermanentTransferDelegate: ForceApproved"); return Ok(ValidationResult::ForceApproved); } } diff --git a/programs/mpl-core/src/plugins/royalties.rs b/programs/mpl-core/src/plugins/royalties.rs index 4b21a0a1..ccd6cd1d 100644 --- a/programs/mpl-core/src/plugins/royalties.rs +++ b/programs/mpl-core/src/plugins/royalties.rs @@ -1,11 +1,11 @@ use std::collections::HashSet; use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; use crate::{error::MplCoreError, plugins::PluginType, state::Authority}; -use super::{Plugin, PluginValidation, ValidationResult}; +use super::{Plugin, PluginValidation, PluginValidationContext, ValidationResult}; /// The creator on an asset and whether or not they are verified. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] @@ -67,32 +67,33 @@ fn validate_royalties(royalties: &Royalties) -> Result Result { validate_royalties(self) } fn validate_transfer( &self, - authority_info: &AccountInfo, - new_owner: &AccountInfo, - _authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { + let new_owner = ctx.new_owner.ok_or(MplCoreError::MissingNewOwner)?; match &self.rule_set { RuleSet::None => Ok(ValidationResult::Pass), RuleSet::ProgramAllowList(allow_list) => { - solana_program::msg!("Evaluating royalties"); - if allow_list.contains(authority_info.owner) || allow_list.contains(new_owner.owner) + if allow_list.contains(ctx.authority_info.owner) + || allow_list.contains(new_owner.owner) { Ok(ValidationResult::Pass) } else { + solana_program::msg!("Royalties: Rejected"); Ok(ValidationResult::Rejected) } } RuleSet::ProgramDenyList(deny_list) => { - if deny_list.contains(authority_info.owner) || deny_list.contains(new_owner.owner) { + if deny_list.contains(ctx.authority_info.owner) + || deny_list.contains(new_owner.owner) + { + solana_program::msg!("Royalties: Rejected"); Ok(ValidationResult::Rejected) } else { Ok(ValidationResult::Pass) @@ -103,27 +104,23 @@ impl PluginValidation for Royalties { fn validate_add_plugin( &self, - _authority: &AccountInfo, - _authorities: &Authority, - _new_plugin: Option<&Plugin>, + _ctx: &PluginValidationContext, ) -> Result { validate_royalties(self) } fn validate_update_plugin( &self, - _authority_info: &AccountInfo, - authority: &Authority, - resolved_authorities: &[Authority], - plugin_to_update: &Plugin, + ctx: &PluginValidationContext, ) -> Result { - solana_program::msg!("authority: {:?}", authority); - solana_program::msg!("resolved_authority: {:?}", resolved_authorities); + let plugin_to_update = ctx.target_plugin.ok_or(MplCoreError::InvalidPlugin)?; + let resolved_authorities = ctx + .resolved_authorities + .ok_or(MplCoreError::InvalidAuthority)?; // Perform validation on the new royalties plugin data. if let Plugin::Royalties(royalties) = plugin_to_update { - if resolved_authorities.contains(authority) { - solana_program::msg!("Validating royalties"); + if resolved_authorities.contains(ctx.self_authority) { validate_royalties(royalties) } else { Ok(ValidationResult::Pass) @@ -136,19 +133,16 @@ impl PluginValidation for Royalties { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - solana_program::msg!("authority_info: {:?}", authority_info.key); - solana_program::msg!("authority: {:?}", authority); - if authority + if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) - && plugin_to_revoke.is_some() - && PluginType::from(plugin_to_revoke.unwrap()) == PluginType::Royalties + && ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::Royalties { + solana_program::msg!("Royalties: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/transfer.rs b/programs/mpl-core/src/plugins/transfer.rs index 424aff32..ca9068e1 100644 --- a/programs/mpl-core/src/plugins/transfer.rs +++ b/programs/mpl-core/src/plugins/transfer.rs @@ -1,12 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::program_error::ProgramError; use crate::{ plugins::PluginType, state::{Authority, DataBlob}, }; -use super::{Plugin, PluginValidation, ValidationResult}; +use super::{PluginValidation, PluginValidationContext, ValidationResult}; /// This plugin manages the ability to transfer an asset and any authorities /// approved are permitted to transfer the asset on behalf of the owner. @@ -40,15 +40,14 @@ impl DataBlob for TransferDelegate { impl PluginValidation for TransferDelegate { fn validate_burn( &self, - authority_info: &AccountInfo, - authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - if authority + if ctx.self_authority == (&Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) { + solana_program::msg!("TransferDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -57,16 +56,14 @@ impl PluginValidation for TransferDelegate { fn validate_transfer( &self, - authority_info: &AccountInfo, - _new_owner: &AccountInfo, - authority: &Authority, - _resolved_authorities: Option<&[Authority]>, + ctx: &PluginValidationContext, ) -> Result { - if authority + if ctx.self_authority == (&Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) { + solana_program::msg!("TransferDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -76,19 +73,16 @@ impl PluginValidation for TransferDelegate { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - solana_program::msg!("authority_info: {:?}", authority_info.key); - solana_program::msg!("authority: {:?}", authority); - if authority + if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) - && plugin_to_revoke.is_some() - && PluginType::from(plugin_to_revoke.unwrap()) == PluginType::TransferDelegate + && ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::TransferDelegate { + solana_program::msg!("TransferDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/plugins/update_delegate.rs b/programs/mpl-core/src/plugins/update_delegate.rs index 64b72da5..be07616b 100644 --- a/programs/mpl-core/src/plugins/update_delegate.rs +++ b/programs/mpl-core/src/plugins/update_delegate.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; use crate::{ error::MplCoreError, @@ -7,7 +7,7 @@ use crate::{ state::{Authority, DataBlob}, }; -use super::{Plugin, PluginValidation, ValidationResult}; +use super::{Plugin, PluginValidation, PluginValidationContext, ValidationResult}; /// This plugin manages additional permissions to burn. /// Any authorities approved are given permission to burn the asset on behalf of the owner. @@ -46,8 +46,7 @@ impl DataBlob for UpdateDelegate { impl PluginValidation for UpdateDelegate { fn validate_create( &self, - _authority_info: &AccountInfo, - _authority: &Authority, + _ctx: &PluginValidationContext, ) -> Result { if !self.additional_delegates.is_empty() { return Err(MplCoreError::NotAvailable.into()); @@ -57,23 +56,22 @@ impl PluginValidation for UpdateDelegate { fn validate_add_plugin( &self, - authority_info: &AccountInfo, - authority: &Authority, - new_plugin: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if let Some(new_plugin) = new_plugin { + if let Some(new_plugin) = ctx.target_plugin { if let Plugin::UpdateDelegate(update_delegate) = new_plugin { if !update_delegate.additional_delegates.is_empty() { return Err(MplCoreError::NotAvailable.into()); } } - if authority + if ctx.self_authority == (&Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) && new_plugin.manager() == Authority::UpdateAuthority { + solana_program::msg!("UpdateDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -85,17 +83,16 @@ impl PluginValidation for UpdateDelegate { fn validate_remove_plugin( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_remove: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - if let Some(plugin_to_remove) = plugin_to_remove { - if authority + if let Some(plugin_to_remove) = ctx.target_plugin { + if ctx.self_authority == (&Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) && plugin_to_remove.manager() == Authority::UpdateAuthority { + solana_program::msg!("UpdateDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -108,19 +105,16 @@ impl PluginValidation for UpdateDelegate { /// Validate the revoke plugin authority lifecycle action. fn validate_revoke_plugin_authority( &self, - authority_info: &AccountInfo, - authority: &Authority, - plugin_to_revoke: Option<&Plugin>, + ctx: &PluginValidationContext, ) -> Result { - solana_program::msg!("authority_info: {:?}", authority_info.key); - solana_program::msg!("authority: {:?}", authority); - if authority + if ctx.self_authority == &(Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) - && plugin_to_revoke.is_some() - && PluginType::from(plugin_to_revoke.unwrap()) == PluginType::UpdateDelegate + && ctx.target_plugin.is_some() + && PluginType::from(ctx.target_plugin.unwrap()) == PluginType::UpdateDelegate { + solana_program::msg!("UpdateDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -129,14 +123,14 @@ impl PluginValidation for UpdateDelegate { fn validate_update( &self, - authority_info: &AccountInfo, - authority: &Authority, + ctx: &PluginValidationContext, ) -> Result { - if authority + if ctx.self_authority == (&Authority::Address { - address: *authority_info.key, + address: *ctx.authority_info.key, }) { + solana_program::msg!("UpdateDelegate: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -145,11 +139,9 @@ impl PluginValidation for UpdateDelegate { fn validate_update_plugin( &self, - _authority_info: &AccountInfo, - _authority: &Authority, - _resolved_authorities: &[Authority], - plugin_to_update: &Plugin, + ctx: &PluginValidationContext, ) -> Result { + let plugin_to_update = ctx.target_plugin.ok_or(MplCoreError::InvalidPlugin)?; if let Plugin::UpdateDelegate(update_delegate) = plugin_to_update { if !update_delegate.additional_delegates.is_empty() { return Err(MplCoreError::NotAvailable.into()); diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index 9ee7d877..7d84b04a 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -141,15 +141,23 @@ pub fn fetch_plugin( /// Fetch the plugin from the registry. pub fn fetch_wrapped_plugin( account: &AccountInfo, + core: Option<&T>, plugin_type: PluginType, ) -> Result<(Authority, Plugin), ProgramError> { - let asset = T::load(account, 0)?; + let size = match core { + Some(core) => core.get_size(), + None => { + let asset = T::load(account, 0)?; - if asset.get_size() == account.data_len() { - return Err(MplCoreError::PluginNotFound.into()); - } + if asset.get_size() == account.data_len() { + return Err(MplCoreError::PluginNotFound.into()); + } - let header = PluginHeaderV1::load(account, asset.get_size())?; + asset.get_size() + } + }; + + let header = PluginHeaderV1::load(account, size)?; let PluginRegistryV1 { registry, .. } = PluginRegistryV1::load(account, header.plugin_registry_offset)?; @@ -300,9 +308,6 @@ pub fn delete_plugin<'a, T: DataBlob>( .checked_sub(serialized_plugin.len()) .ok_or(MplCoreError::NumericalOverflow)?; - solana_program::msg!("size: {:?}", account.data_len()); - solana_program::msg!("new_size: {:?}", new_size); - let new_registry_offset = header .plugin_registry_offset .checked_sub(serialized_plugin.len()) diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index b0ceffd2..c6ce277d 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -5,7 +5,10 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg}; use crate::{ error::MplCoreError, instruction::accounts::{AddCollectionPluginV1Accounts, AddPluginV1Accounts}, - plugins::{create_meta_idempotent, initialize_plugin, Plugin, PluginType, ValidationResult}, + plugins::{ + create_meta_idempotent, initialize_plugin, Plugin, PluginType, PluginValidationContext, + ValidationResult, + }, state::{AssetV1, Authority, CollectionV1, DataBlob, Key, SolanaAccount}, utils::{ load_key, resolve_authority, validate_asset_permissions, validate_collection_permissions, @@ -45,15 +48,14 @@ pub(crate) fn add_plugin<'a>( } //TODO: Seed with Rejected - if Plugin::validate_add_plugin( - &args.plugin, - authority, - None, - &args.init_authority.unwrap_or(args.plugin.manager()), - Some(&args.plugin), - None, - )? == ValidationResult::Rejected - { + let validation_ctx = PluginValidationContext { + self_authority: &args.init_authority.unwrap_or(args.plugin.manager()), + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: Some(&args.plugin), + }; + if Plugin::validate_add_plugin(&args.plugin, &validation_ctx)? == ValidationResult::Rejected { return Err(MplCoreError::InvalidAuthority.into()); } @@ -111,14 +113,14 @@ pub(crate) fn add_collection_plugin<'a>( } } - if Plugin::validate_add_plugin( - &args.plugin, - authority, - None, - &args.init_authority.unwrap_or(args.plugin.manager()), - Some(&args.plugin), - None, - )? == ValidationResult::Rejected + let validation_context = PluginValidationContext { + self_authority: &args.init_authority.unwrap_or(args.plugin.manager()), + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: Some(&args.plugin), + }; + if Plugin::validate_add_plugin(&args.plugin, &validation_context)? == ValidationResult::Rejected { return Err(MplCoreError::InvalidAuthority.into()); } @@ -155,10 +157,8 @@ fn process_add_plugin<'a, T: DataBlob + SolanaAccount>( plugin: &Plugin, authority: &Authority, ) -> ProgramResult { - solana_program::msg!("Creating meta if it doesn't exist"); let (_, mut plugin_header, mut plugin_registry) = create_meta_idempotent::(account, payer, system_program)?; - solana_program::msg!("Initializing plugin"); initialize_plugin::( plugin, authority, @@ -168,6 +168,5 @@ fn process_add_plugin<'a, T: DataBlob + SolanaAccount>( payer, system_program, )?; - solana_program::msg!("Plugin added successfully"); Ok(()) } diff --git a/programs/mpl-core/src/processor/approve_plugin_authority.rs b/programs/mpl-core/src/processor/approve_plugin_authority.rs index 6b7f9490..9ddf456f 100644 --- a/programs/mpl-core/src/processor/approve_plugin_authority.rs +++ b/programs/mpl-core/src/processor/approve_plugin_authority.rs @@ -47,7 +47,7 @@ pub(crate) fn approve_plugin_authority<'a>( return Err(MplCoreError::NotAvailable.into()); } - let (_, plugin) = fetch_wrapped_plugin::(ctx.accounts.asset, args.plugin_type)?; + let (_, plugin) = fetch_wrapped_plugin::(ctx.accounts.asset, None, args.plugin_type)?; // Validate asset permissions. let (mut asset, _, _) = validate_asset_permissions( @@ -104,7 +104,7 @@ pub(crate) fn approve_collection_plugin_authority<'a>( } let (_, plugin) = - fetch_wrapped_plugin::(ctx.accounts.collection, args.plugin_type)?; + fetch_wrapped_plugin::(ctx.accounts.collection, None, args.plugin_type)?; // Validate collection permissions. let _ = validate_collection_permissions( diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index f7ea591d..cf95aff0 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -10,7 +10,7 @@ use crate::{ instruction::accounts::CreateV2Accounts, plugins::{ create_plugin_meta, initialize_plugin, CheckResult, ExternalPluginInitInfo, Plugin, - PluginAuthorityPair, PluginType, ValidationResult, + PluginAuthorityPair, PluginType, PluginValidationContext, ValidationResult, }, state::{AssetV1, CollectionV1, DataState, SolanaAccount, UpdateAuthority, COLLECT_AMOUNT}, utils::resolve_authority, @@ -67,7 +67,7 @@ pub(crate) fn process_create<'a>( assert_signer(ctx.accounts.payer)?; let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; - if *ctx.accounts.system_program.key != system_program::id() { + if *ctx.accounts.system_program.key != system_program::ID { return Err(MplCoreError::InvalidSystemProgram.into()); } @@ -131,7 +131,7 @@ pub(crate) fn process_create<'a>( ctx.accounts.asset.key, lamports, serialized_data.len() as u64, - &crate::id(), + &crate::ID, ), &[ ctx.accounts.payer.clone(), @@ -159,14 +159,14 @@ pub(crate) fn process_create<'a>( for plugin in &plugins { if PluginType::check_create(&PluginType::from(&plugin.plugin)) != CheckResult::None { - match Plugin::validate_create( - &plugin.plugin, - authority, - None, - &plugin.authority.unwrap_or(plugin.plugin.manager()), - None, - None, - )? { + let validation_ctx = PluginValidationContext { + self_authority: &plugin.authority.unwrap_or(plugin.plugin.manager()), + authority_info: authority, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + match Plugin::validate_create(&plugin.plugin, &validation_ctx)? { ValidationResult::Rejected => approved = false, ValidationResult::ForceApproved => force_approved = true, _ => (), diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index 886b0bc7..2971492a 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -10,7 +10,7 @@ use crate::{ instruction::accounts::CreateCollectionV2Accounts, plugins::{ create_plugin_meta, initialize_plugin, CheckResult, ExternalPluginInitInfo, Plugin, - PluginAuthorityPair, PluginType, ValidationResult, + PluginAuthorityPair, PluginType, PluginValidationContext, ValidationResult, }, state::{Authority, CollectionV1, Key}, }; @@ -68,7 +68,7 @@ pub(crate) fn process_create_collection<'a>( assert_signer(ctx.accounts.collection)?; assert_signer(ctx.accounts.payer)?; - if *ctx.accounts.system_program.key != system_program::id() { + if *ctx.accounts.system_program.key != system_program::ID { return Err(MplCoreError::InvalidSystemProgram.into()); } @@ -96,7 +96,7 @@ pub(crate) fn process_create_collection<'a>( ctx.accounts.collection.key, lamports, serialized_data.len() as u64, - &crate::id(), + &crate::ID, ), &[ ctx.accounts.payer.clone(), @@ -134,14 +134,14 @@ pub(crate) fn process_create_collection<'a>( } if PluginType::check_create(&plugin_type) != CheckResult::None { - match Plugin::validate_create( - &plugin.plugin, - ctx.accounts.payer, - None, - &plugin.authority.unwrap_or(plugin.plugin.manager()), - None, - None, - )? { + let validation_ctx = PluginValidationContext { + self_authority: &plugin.authority.unwrap_or(plugin.plugin.manager()), + authority_info: ctx.accounts.payer, + resolved_authorities: None, + new_owner: None, + target_plugin: None, + }; + match Plugin::validate_create(&plugin.plugin, &validation_ctx)? { ValidationResult::Rejected => approved = false, ValidationResult::ForceApproved => force_approved = true, _ => (), diff --git a/programs/mpl-core/src/processor/decompress.rs b/programs/mpl-core/src/processor/decompress.rs index 0b309c2a..a4c5953c 100644 --- a/programs/mpl-core/src/processor/decompress.rs +++ b/programs/mpl-core/src/processor/decompress.rs @@ -30,7 +30,7 @@ pub(crate) fn decompress<'a>( assert_signer(ctx.accounts.payer)?; let authority = resolve_authority(ctx.accounts.payer, ctx.accounts.authority)?; - if *ctx.accounts.system_program.key != system_program::id() { + if *ctx.accounts.system_program.key != system_program::ID { return Err(MplCoreError::InvalidSystemProgram.into()); } diff --git a/programs/mpl-core/src/processor/remove_plugin.rs b/programs/mpl-core/src/processor/remove_plugin.rs index e1bb81b5..caa2bbea 100644 --- a/programs/mpl-core/src/processor/remove_plugin.rs +++ b/programs/mpl-core/src/processor/remove_plugin.rs @@ -53,7 +53,7 @@ pub(crate) fn remove_plugin<'a>( } let (_, plugin_to_remove) = - fetch_wrapped_plugin::(ctx.accounts.asset, args.plugin_type)?; + fetch_wrapped_plugin::(ctx.accounts.asset, Some(&asset), args.plugin_type)?; // Validate asset permissions. let _ = validate_asset_permissions( @@ -116,8 +116,11 @@ pub(crate) fn remove_collection_plugin<'a>( return Err(MplCoreError::PluginNotFound.into()); } - let (_, plugin_to_remove) = - fetch_wrapped_plugin::(ctx.accounts.collection, args.plugin_type)?; + let (_, plugin_to_remove) = fetch_wrapped_plugin::( + ctx.accounts.collection, + Some(&collection), + args.plugin_type, + )?; // Validate collection permissions. let _ = validate_collection_permissions( diff --git a/programs/mpl-core/src/processor/revoke_plugin_authority.rs b/programs/mpl-core/src/processor/revoke_plugin_authority.rs index 10d2ae89..fbee760e 100644 --- a/programs/mpl-core/src/processor/revoke_plugin_authority.rs +++ b/programs/mpl-core/src/processor/revoke_plugin_authority.rs @@ -53,7 +53,8 @@ pub(crate) fn revoke_plugin_authority<'a>( let (mut asset, plugin_header, mut plugin_registry) = fetch_core_data::(ctx.accounts.asset)?; - let (_, plugin) = fetch_wrapped_plugin::(ctx.accounts.asset, args.plugin_type)?; + let (_, plugin) = + fetch_wrapped_plugin::(ctx.accounts.asset, Some(&asset), args.plugin_type)?; // Validate asset permissions. let _ = validate_asset_permissions( @@ -117,11 +118,14 @@ pub(crate) fn revoke_collection_plugin_authority<'a>( } } - let (_, plugin_header, mut plugin_registry) = + let (collection, plugin_header, mut plugin_registry) = fetch_core_data::(ctx.accounts.collection)?; - let (_, plugin) = - fetch_wrapped_plugin::(ctx.accounts.collection, args.plugin_type)?; + let (_, plugin) = fetch_wrapped_plugin::( + ctx.accounts.collection, + Some(&collection), + args.plugin_type, + )?; // Validate collection permissions. let _ = validate_collection_permissions( diff --git a/programs/mpl-core/src/processor/transfer.rs b/programs/mpl-core/src/processor/transfer.rs index adaa3b0c..b84d47f8 100644 --- a/programs/mpl-core/src/processor/transfer.rs +++ b/programs/mpl-core/src/processor/transfer.rs @@ -94,11 +94,11 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferV1Args if let (Some(plugin_header), Some(mut plugin_registry)) = (plugin_header, plugin_registry.clone()) { - for record in plugin_registry.registry.iter_mut() { + plugin_registry.registry.iter_mut().for_each(|record| { if record.plugin_type.manager() == Authority::Owner { record.authority = Authority::Owner; } - } + }); // Save the plugin registry. plugin_registry.save(ctx.accounts.asset, plugin_header.plugin_registry_offset)?; diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index b8f8b7fc..43fd4fd5 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -130,6 +130,7 @@ impl AssetV1 { || (UpdateAuthority::Address(*authority_info.key) == self.update_authority && new_plugin.manager() == Authority::UpdateAuthority) { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -140,9 +141,18 @@ impl AssetV1 { pub fn validate_remove_plugin( &self, authority_info: &AccountInfo, - _plugin_to_remove: Option<&Plugin>, + plugin_to_remove: Option<&Plugin>, ) -> Result { - if authority_info.key == &self.owner { + let plugin = match plugin_to_remove { + Some(plugin) => plugin, + None => return Err(MplCoreError::InvalidPlugin.into()), + }; + + if (plugin.manager() == Authority::UpdateAuthority + && self.update_authority == UpdateAuthority::Address(*authority_info.key)) + || (plugin.manager() == Authority::Owner && authority_info.key == &self.owner) + { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -169,6 +179,7 @@ impl AssetV1 { && self.update_authority == UpdateAuthority::Address(*authority_info.key)) || (plugin.manager() == Authority::Owner && authority_info.key == &self.owner) { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -189,6 +200,7 @@ impl AssetV1 { && self.update_authority == UpdateAuthority::Address(*authority_info.key)) || (plugin.manager() == Authority::Owner && authority_info.key == &self.owner) { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -205,6 +217,7 @@ impl AssetV1 { _: Option<&Plugin>, ) -> Result { if authority_info.key == &self.update_authority.key() { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -218,6 +231,7 @@ impl AssetV1 { _: Option<&Plugin>, ) -> Result { if authority_info.key == &self.owner { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -231,6 +245,7 @@ impl AssetV1 { _: Option<&Plugin>, ) -> Result { if authority_info.key == &self.owner { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -244,6 +259,7 @@ impl AssetV1 { _: Option<&Plugin>, ) -> Result { if authority_info.key == &self.owner { + solana_program::msg!("Asset: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -257,6 +273,7 @@ impl AssetV1 { _: Option<&Plugin>, ) -> Result { if authority_info.key == &self.owner { + solana_program::msg!("Asset: Approved"); 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 c94ecbc3..8ec0cede 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -112,6 +112,7 @@ impl CollectionV1 { if *authority_info.key == self.update_authority && new_plugin.manager() == Authority::UpdateAuthority { + solana_program::msg!("Collection: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -132,6 +133,7 @@ impl CollectionV1 { if *authority_info.key == self.update_authority && plugin_to_remove.manager() == Authority::UpdateAuthority { + solana_program::msg!("Collection: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -161,6 +163,7 @@ impl CollectionV1 { if *authority_info.key == self.update_authority && plugin.manager() == Authority::UpdateAuthority { + solana_program::msg!("Collection: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -181,6 +184,7 @@ impl CollectionV1 { if *authority_info.key == self.update_authority && plugin.manager() == Authority::UpdateAuthority { + solana_program::msg!("Collection: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) @@ -212,6 +216,7 @@ impl CollectionV1 { _: Option<&Plugin>, ) -> Result { if authority_info.key == &self.update_authority { + solana_program::msg!("Collection: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/state/update_authority.rs b/programs/mpl-core/src/state/update_authority.rs index a08b949f..800163a0 100644 --- a/programs/mpl-core/src/state/update_authority.rs +++ b/programs/mpl-core/src/state/update_authority.rs @@ -58,7 +58,6 @@ impl UpdateAuthority { return Err(MplCoreError::InvalidCollection.into()); } let collection = CollectionV1::load(collection_info, 0)?; - solana_program::msg!("Collection: {:?}", collection); let authority_info = match ctx.authority { Some(authority) => { @@ -82,9 +81,11 @@ impl UpdateAuthority { ) .is_err() { + solana_program::msg!("UA: Rejected"); return Ok(ValidationResult::Rejected); } } else if authority_info.key != &collection.update_authority { + solana_program::msg!("UA: Rejected"); return Ok(ValidationResult::Rejected); } @@ -109,6 +110,7 @@ impl UpdateAuthority { }; if ctx.authority.unwrap_or(ctx.payer).key == authority { + solana_program::msg!("UA: Approved"); Ok(ValidationResult::Approved) } else { Ok(ValidationResult::Pass) diff --git a/programs/mpl-core/src/utils.rs b/programs/mpl-core/src/utils.rs index 18aafd90..5cc4588a 100644 --- a/programs/mpl-core/src/utils.rs +++ b/programs/mpl-core/src/utils.rs @@ -13,7 +13,8 @@ use crate::{ error::MplCoreError, plugins::{ create_meta_idempotent, initialize_plugin, validate_plugin_checks, CheckResult, Plugin, - PluginHeaderV1, PluginRegistryV1, PluginType, RegistryRecord, ValidationResult, + PluginHeaderV1, PluginRegistryV1, PluginType, PluginValidationContext, RegistryRecord, + ValidationResult, }, state::{ AssetV1, Authority, CollectionV1, Compressible, CompressionProof, CoreAsset, DataBlob, @@ -193,11 +194,11 @@ pub(crate) fn resize_or_reallocate_account<'a>( #[allow(clippy::too_many_arguments, clippy::type_complexity)] /// Validate asset permissions using lifecycle validations for asset, collection, and plugins. -pub fn validate_asset_permissions<'a>( - authority_info: &AccountInfo<'a>, +pub(crate) fn validate_asset_permissions<'a>( + authority_info: &'a AccountInfo<'a>, asset: &AccountInfo<'a>, collection: Option<&AccountInfo<'a>>, - new_owner: Option<&AccountInfo<'a>>, + new_owner: Option<&'a AccountInfo<'a>>, new_plugin: Option<&Plugin>, asset_check_fp: fn() -> CheckResult, collection_check_fp: fn() -> CheckResult, @@ -214,11 +215,7 @@ pub fn validate_asset_permissions<'a>( ) -> Result, plugin_validate_fp: fn( &Plugin, - &AccountInfo, - Option<&AccountInfo>, - &Authority, - Option<&Plugin>, - Option<&[Authority]>, + &PluginValidationContext, ) -> Result, ) -> Result<(AssetV1, Option, Option), ProgramError> { let (deserialized_asset, plugin_header, plugin_registry) = fetch_core_data::(asset)?; @@ -232,6 +229,8 @@ pub fn validate_asset_permissions<'a>( } else if collection.unwrap().key != &collection_address { return Err(MplCoreError::InvalidCollection.into()); } + } else if collection.is_some() { + return Err(MplCoreError::InvalidCollection.into()); } let mut checks: BTreeMap = BTreeMap::new(); @@ -260,8 +259,6 @@ pub fn validate_asset_permissions<'a>( registry.check_registry(Key::AssetV1, plugin_check_fp, &mut checks); } - solana_program::msg!("checks: {:#?}", checks); - // Do the core validation. let mut approved = false; let mut rejected = false; @@ -275,7 +272,6 @@ pub fn validate_asset_permissions<'a>( } } }; - solana_program::msg!("approved: {:?} rejected {:?}", approved, rejected); if collection_check != CheckResult::None { match collection_validate_fp( @@ -291,7 +287,6 @@ pub fn validate_asset_permissions<'a>( } } }; - solana_program::msg!("approved: {:?} rejected {:?}", approved, rejected); match validate_plugin_checks( Key::CollectionV1, @@ -312,8 +307,6 @@ pub fn validate_asset_permissions<'a>( } }; - solana_program::msg!("approved: {:?} rejected {:?}", approved, rejected); - match validate_plugin_checks( Key::AssetV1, &checks, @@ -333,8 +326,6 @@ pub fn validate_asset_permissions<'a>( } }; - solana_program::msg!("approved: {:?} rejected {:?}", approved, rejected); - if rejected { return Err(MplCoreError::InvalidAuthority.into()); } else if !approved { @@ -346,8 +337,8 @@ pub fn validate_asset_permissions<'a>( /// Validate collection permissions using lifecycle validations for collection and plugins. #[allow(clippy::type_complexity)] -pub fn validate_collection_permissions<'a>( - authority_info: &AccountInfo<'a>, +pub(crate) fn validate_collection_permissions<'a>( + authority_info: &'a AccountInfo<'a>, collection: &AccountInfo<'a>, new_plugin: Option<&Plugin>, collection_check_fp: fn() -> CheckResult, @@ -359,11 +350,7 @@ pub fn validate_collection_permissions<'a>( ) -> Result, plugin_validate_fp: fn( &Plugin, - &AccountInfo, - Option<&AccountInfo>, - &Authority, - Option<&Plugin>, - Option<&[Authority]>, + &PluginValidationContext, ) -> Result, ) -> Result< ( @@ -386,8 +373,6 @@ pub fn validate_collection_permissions<'a>( registry.check_registry(Key::CollectionV1, plugin_check_fp, &mut checks); } - solana_program::msg!("checks: {:#?}", checks); - // Do the core validation. let mut approved = false; let mut rejected = false; @@ -413,7 +398,6 @@ pub fn validate_collection_permissions<'a>( } } }; - solana_program::msg!("approved: {:?} rejected {:?}", approved, rejected); match validate_plugin_checks( Key::CollectionV1, @@ -434,8 +418,6 @@ pub fn validate_collection_permissions<'a>( } }; - solana_program::msg!("approved: {:?} rejected {:?}", approved, rejected); - if rejected || !approved { return Err(MplCoreError::InvalidAuthority.into()); } @@ -542,7 +524,7 @@ pub(crate) fn resolve_pubkey_to_authorities( maybe_collection_info: Option<&AccountInfo>, asset: &AssetV1, ) -> Result, ProgramError> { - let mut authorities = vec![]; + let mut authorities = Vec::with_capacity(3); if authority_info.key == &asset.owner { authorities.push(Authority::Owner); } @@ -576,7 +558,7 @@ pub(crate) fn resolve_pubkey_to_authorities_collection( collection_info: &AccountInfo, ) -> Result, ProgramError> { let collection: CollectionV1 = CollectionV1::load(collection_info, 0)?; - let mut authorities = vec![]; + let mut authorities = Vec::with_capacity(3); if authority_info.key == collection.owner() { authorities.push(Authority::Owner); }