From 001301cb6781f9cd60e35147cab2c43c0017cde8 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:59:11 -0700 Subject: [PATCH] Fix update delegate plugin permission (#47) --- .../plugins/asset/delegateTransfer.test.ts | 2 +- .../test/plugins/asset/updateDelegate.test.ts | 133 +++++++++++++++ .../plugins/collection/updateDelegate.test.ts | 151 ++++++++++++++++++ programs/mpl-core/src/plugins/lifecycle.rs | 1 + .../mpl-core/src/plugins/update_delegate.rs | 16 ++ 5 files changed, 302 insertions(+), 1 deletion(-) diff --git a/clients/js/test/plugins/asset/delegateTransfer.test.ts b/clients/js/test/plugins/asset/delegateTransfer.test.ts index 1eddd5c8..99415273 100644 --- a/clients/js/test/plugins/asset/delegateTransfer.test.ts +++ b/clients/js/test/plugins/asset/delegateTransfer.test.ts @@ -115,7 +115,7 @@ test('it can revoke a delegate transfer plugin', async (t) => { }); }); -test('it cannot transfer after delegate has been revoked', async (t) => { +test('it cannot transfer after delegate authority has been revoked', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); const delegateAddress = generateSigner(umi); diff --git a/clients/js/test/plugins/asset/updateDelegate.test.ts b/clients/js/test/plugins/asset/updateDelegate.test.ts index c4c22497..53b32096 100644 --- a/clients/js/test/plugins/asset/updateDelegate.test.ts +++ b/clients/js/test/plugins/asset/updateDelegate.test.ts @@ -5,6 +5,10 @@ import { pluginAuthorityPair, addPluginV1, updatePluginV1, + updateV1, + addressPluginAuthority, + revokePluginAuthorityV1, + PluginType, } from '../../../src'; import { DEFAULT_ASSET, @@ -123,3 +127,132 @@ test('it cannot update updateDelegate on asset with additional delegates', async }, }); }); + +test('it updateDelegate can update an asset', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi, { + name: 'short', + uri: 'https://short.com', + }); + const updateDelegate = generateSigner(umi); + + await addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'UpdateDelegate', + }), + initAuthority: addressPluginAuthority(updateDelegate.publicKey), + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + updateDelegate: { + authority: { + type: 'Address', + address: updateDelegate.publicKey, + }, + additionalDelegates: [], + }, + name: 'short', + uri: 'https://short.com', + }); + + await updateV1(umi, { + asset: asset.publicKey, + newName: 'Test Bread 2', + newUri: 'https://example.com/bread2', + authority: updateDelegate, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + updateDelegate: { + authority: { + type: 'Address', + address: updateDelegate.publicKey, + }, + additionalDelegates: [], + }, + name: 'Test Bread 2', + uri: 'https://example.com/bread2', + }); +}); + +test('it updateDelegate cannot update an asset after delegate authority revoked', async (t) => { + const umi = await createUmi(); + const asset = await createAsset(umi, { + name: 'short', + uri: 'https://short.com', + }); + const updateDelegate = generateSigner(umi); + + await addPluginV1(umi, { + asset: asset.publicKey, + plugin: createPlugin({ + type: 'UpdateDelegate', + }), + initAuthority: addressPluginAuthority(updateDelegate.publicKey), + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + updateDelegate: { + authority: { + type: 'Address', + address: updateDelegate.publicKey, + }, + additionalDelegates: [], + }, + name: 'short', + uri: 'https://short.com', + }); + + await revokePluginAuthorityV1(umi, { + asset: asset.publicKey, + pluginType: PluginType.UpdateDelegate, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + updateDelegate: { + authority: { + type: 'UpdateAuthority', + }, + additionalDelegates: [], + }, + name: 'short', + uri: 'https://short.com', + }); + + const result = updateV1(umi, { + asset: asset.publicKey, + newName: 'Test Bread 2', + newUri: 'https://example.com/bread2', + authority: updateDelegate, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'NoApprovals' }); + + await assertAsset(t, umi, { + asset: asset.publicKey, + owner: umi.identity.publicKey, + updateAuthority: { type: 'Address', address: umi.identity.publicKey }, + updateDelegate: { + authority: { + type: 'UpdateAuthority', + }, + additionalDelegates: [], + }, + name: 'short', + uri: 'https://short.com', + }); +}); diff --git a/clients/js/test/plugins/collection/updateDelegate.test.ts b/clients/js/test/plugins/collection/updateDelegate.test.ts index 7f2594d6..b7fed683 100644 --- a/clients/js/test/plugins/collection/updateDelegate.test.ts +++ b/clients/js/test/plugins/collection/updateDelegate.test.ts @@ -8,7 +8,9 @@ import { createPlugin, pluginAuthorityPair, addressPluginAuthority, + updateV1, updateCollectionPluginV1, + revokeCollectionPluginAuthorityV1, } from '../../../src'; import { DEFAULT_ASSET, @@ -186,3 +188,152 @@ test('it cannot update updateDelegate on collection with additional delegates', }, }); }); + +test('it updateDelegate on collection can update an asset', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const updateDelegate = await generateSignerWithSol(umi); + + // When we create a new account. + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'UpdateDelegate', + }), + ], + }); + + await approveCollectionPluginAuthorityV1(umi, { + collection: collection.publicKey, + pluginType: PluginType.UpdateDelegate, + newAuthority: addressPluginAuthority(updateDelegate.publicKey), + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + updateDelegate: { + authority: { + type: 'Address', + address: updateDelegate.publicKey, + }, + additionalDelegates: [], + }, + }); + + umi.identity = updateDelegate; + umi.payer = updateDelegate; + const owner = generateSigner(umi); + const asset = await createAsset(umi, { + collection: collection.publicKey, + owner, + authority: updateDelegate, + name: 'short', + uri: 'https://short.com', + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + updateAuthority: { type: 'Collection', address: collection.publicKey }, + name: 'short', + uri: 'https://short.com', + }); + + await updateV1(umi, { + asset: asset.publicKey, + collection: collection.publicKey, + newName: 'Test Bread 2', + newUri: 'https://example.com/bread2', + authority: updateDelegate, + }).sendAndConfirm(umi); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + updateAuthority: { type: 'Collection', address: collection.publicKey }, + name: 'Test Bread 2', + uri: 'https://example.com/bread2', + }); +}); + +test('it updateDelegate on collection cannot update an asset after delegate authority revoked', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const updateDelegate = await generateSignerWithSol(umi); + + // When we create a new account. + const collection = await createCollection(umi, { + plugins: [ + pluginAuthorityPair({ + type: 'UpdateDelegate', + }), + ], + }); + + await approveCollectionPluginAuthorityV1(umi, { + collection: collection.publicKey, + pluginType: PluginType.UpdateDelegate, + newAuthority: addressPluginAuthority(updateDelegate.publicKey), + }).sendAndConfirm(umi); + + await assertCollection(t, umi, { + ...DEFAULT_COLLECTION, + collection: collection.publicKey, + updateAuthority: umi.identity.publicKey, + updateDelegate: { + authority: { + type: 'Address', + address: updateDelegate.publicKey, + }, + additionalDelegates: [], + }, + }); + + umi.identity = updateDelegate; + umi.payer = updateDelegate; + const owner = generateSigner(umi); + const asset = await createAsset(umi, { + collection: collection.publicKey, + owner, + authority: updateDelegate, + name: 'short', + uri: 'https://short.com', + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + updateAuthority: { type: 'Collection', address: collection.publicKey }, + name: 'short', + uri: 'https://short.com', + }); + + await revokeCollectionPluginAuthorityV1(umi, { + collection: collection.publicKey, + pluginType: PluginType.UpdateDelegate, + }).sendAndConfirm(umi); + + let result = updateV1(umi, { + asset: asset.publicKey, + collection: collection.publicKey, + newName: 'Test Bread 2', + newUri: 'https://example.com/bread2', + authority: updateDelegate, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'NoApprovals' }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: owner.publicKey, + updateAuthority: { type: 'Collection', address: collection.publicKey }, + name: 'short', + uri: 'https://short.com', + }); +}); diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index 26ae16f8..28e3292f 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -87,6 +87,7 @@ impl PluginType { pub fn check_update(plugin_type: &PluginType) -> CheckResult { #[allow(clippy::match_single_binding)] match plugin_type { + PluginType::UpdateDelegate => CheckResult::CanApprove, _ => CheckResult::None, } } diff --git a/programs/mpl-core/src/plugins/update_delegate.rs b/programs/mpl-core/src/plugins/update_delegate.rs index 3503cc30..64b72da5 100644 --- a/programs/mpl-core/src/plugins/update_delegate.rs +++ b/programs/mpl-core/src/plugins/update_delegate.rs @@ -127,6 +127,22 @@ impl PluginValidation for UpdateDelegate { } } + fn validate_update( + &self, + authority_info: &AccountInfo, + authority: &Authority, + ) -> Result { + if authority + == (&Authority::Address { + address: *authority_info.key, + }) + { + Ok(ValidationResult::Approved) + } else { + Ok(ValidationResult::Pass) + } + } + fn validate_update_plugin( &self, _authority_info: &AccountInfo,