Skip to content

Commit

Permalink
Fixing update.
Browse files Browse the repository at this point in the history
  • Loading branch information
blockiosaurus committed Feb 23, 2024
1 parent af2f47b commit cf1388b
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 21 deletions.
48 changes: 37 additions & 11 deletions clients/js/src/hooked/fetchAssetWithPlugins.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
import { Context, Pda, PublicKey, RpcGetAccountOptions, assertAccountExists, publicKey as toPublicKey } from "@metaplex-foundation/umi";
import { Asset, Authority, Plugin, PluginHeader, PluginRegistry, deserializeAsset, getAssetAccountDataSerializer, getPluginHeaderAccountDataSerializer, getPluginRegistryAccountDataSerializer, getPluginSerializer } from "../generated";
import {
Context,
Pda,
PublicKey,
RpcGetAccountOptions,
assertAccountExists,
publicKey as toPublicKey
} from "@metaplex-foundation/umi";
import {
Asset,
Authority,
Plugin,
PluginHeader,
PluginHeaderAccountData,
PluginRegistry,
PluginRegistryAccountData,
deserializeAsset,
getAssetAccountDataSerializer,
getPluginHeaderAccountDataSerializer,
getPluginRegistryAccountDataSerializer,
getPluginSerializer
} from "../generated";

export type PluginWithAuthorities = { plugin: Plugin, authorities: Authority[] };

export type PluginList = {
pluginHeader: Omit<PluginHeader, 'publicKey' | 'header'>,
plugins: PluginWithAuthorities[],
pluginRegistry: Omit<PluginRegistry, 'publicKey' | 'header'>,
pluginHeader?: Omit<PluginHeader, 'publicKey' | 'header'>,
plugins?: PluginWithAuthorities[],
pluginRegistry?: Omit<PluginRegistry, 'publicKey' | 'header'>,
};
export type AssetWithPlugins = Asset & PluginList;

Expand All @@ -22,12 +42,18 @@ export async function fetchAssetWithPlugins(
assertAccountExists(maybeAccount, 'Asset');
const asset = deserializeAsset(maybeAccount);
const assetData = getAssetAccountDataSerializer().serialize(asset);
const pluginHeader = getPluginHeaderAccountDataSerializer().deserialize(maybeAccount.data, assetData.length)[0];
const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(maybeAccount.data, Number(pluginHeader.pluginRegistryOffset))[0];
const plugins = pluginRegistry.registry.map((record) => ({
plugin: getPluginSerializer().deserialize(maybeAccount.data, Number(record.data.offset))[0],
authorities: record.data.authorities,
}));

let pluginHeader: PluginHeaderAccountData | undefined;
let pluginRegistry: PluginRegistryAccountData | undefined;
let plugins: PluginWithAuthorities[] | undefined;
if (maybeAccount.data.length !== assetData.length) {
[pluginHeader] = getPluginHeaderAccountDataSerializer().deserialize(maybeAccount.data, assetData.length);
[pluginRegistry] = getPluginRegistryAccountDataSerializer().deserialize(maybeAccount.data, Number(pluginHeader.pluginRegistryOffset));
plugins = pluginRegistry.registry.map((record) => ({
plugin: getPluginSerializer().deserialize(maybeAccount.data, Number(record.data.offset))[0],
authorities: record.data.authorities,
}));
}

const assetWithPlugins: AssetWithPlugins = {
pluginHeader,
Expand Down
184 changes: 184 additions & 0 deletions clients/js/test/update.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { generateSigner } from '@metaplex-foundation/umi';
import test from 'ava';
// import { base58 } from '@metaplex-foundation/umi/serializers';
import { AssetWithPlugins, DataState, Key, addPlugin, create, fetchAssetWithPlugins, update } from '../src';
import { createUmi } from './_setup';

test('it can update an asset to be larger', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const assetAddress = generateSigner(umi);

// When we create a new account.
await create(umi, {
dataState: DataState.AccountState,
assetAddress,
name: 'Test Bread',
uri: 'https://example.com/bread',
}).sendAndConfirm(umi);

await update(umi, {
assetAddress: assetAddress.publicKey,
newName: 'Test Bread 2',
newUri: 'https://example.com/bread2',
}).sendAndConfirm(umi);

// Then an account was created with the correct data.
const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey);
t.like(asset, <AssetWithPlugins>{
key: Key.Asset,
updateAuthority: umi.identity.publicKey,
owner: umi.identity.publicKey,
name: "Test Bread 2",
uri: "https://example.com/bread2"
});
});

test('it can update an asset to be smaller', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const assetAddress = generateSigner(umi);

// When we create a new account.
await create(umi, {
dataState: DataState.AccountState,
assetAddress,
name: 'Test Bread',
uri: 'https://example.com/bread',
}).sendAndConfirm(umi);

await update(umi, {
assetAddress: assetAddress.publicKey,
newName: '',
newUri: '',
}).sendAndConfirm(umi);

// Then an account was created with the correct data.
const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey);
t.like(asset, <AssetWithPlugins>{
key: Key.Asset,
updateAuthority: umi.identity.publicKey,
owner: umi.identity.publicKey,
name: "",
uri: ""
});
});

test('it can update an asset with plugins to be larger', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const assetAddress = generateSigner(umi);

// When we create a new account.
await create(umi, {
dataState: DataState.AccountState,
assetAddress,
name: 'Test Bread',
uri: 'https://example.com/bread',
}).sendAndConfirm(umi);

await addPlugin(umi, {
assetAddress: assetAddress.publicKey,
plugin: {
__kind: 'Freeze',
fields: [{ frozen: false }],
}
}).sendAndConfirm(umi);

await update(umi, {
assetAddress: assetAddress.publicKey,
newName: 'Test Bread 2',
newUri: 'https://example.com/bread2',
}).sendAndConfirm(umi);

// Then an account was created with the correct data.
const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey);
// console.log(JSON.stringify(asset, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2));
t.like(asset, <AssetWithPlugins>{
publicKey: assetAddress.publicKey,
updateAuthority: umi.identity.publicKey,
owner: umi.identity.publicKey,
name: 'Test Bread 2',
uri: 'https://example.com/bread2',
pluginHeader: {
key: 3,
pluginRegistryOffset: BigInt(122),
},
pluginRegistry: {
key: 4,
registry: [{
pluginType: 2,
data: {
offset: BigInt(120),
authorities: [{ __kind: "Owner" }]
}
}],
},
plugins: [{
authorities: [{ __kind: "Owner" }],
plugin: {
__kind: 'Freeze',
fields: [{ frozen: false }],
},
}],
});
});

test('it can update an asset with plugins to be smaller', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const assetAddress = generateSigner(umi);

// When we create a new account.
await create(umi, {
dataState: DataState.AccountState,
assetAddress,
name: 'Test Bread',
uri: 'https://example.com/bread',
}).sendAndConfirm(umi);

await addPlugin(umi, {
assetAddress: assetAddress.publicKey,
plugin: {
__kind: 'Freeze',
fields: [{ frozen: false }],
}
}).sendAndConfirm(umi);

await update(umi, {
assetAddress: assetAddress.publicKey,
newName: '',
newUri: '',
}).sendAndConfirm(umi);

// Then an account was created with the correct data.
const asset = await fetchAssetWithPlugins(umi, assetAddress.publicKey);
t.like(asset, <AssetWithPlugins>{
publicKey: assetAddress.publicKey,
updateAuthority: umi.identity.publicKey,
owner: umi.identity.publicKey,
name: '',
uri: '',
pluginHeader: {
key: 3,
pluginRegistryOffset: BigInt(84),
},
pluginRegistry: {
key: 4,
registry: [{
pluginType: 2,
data: {
offset: BigInt(82),
authorities: [{ __kind: "Owner" }]
}
}],
},
plugins: [{
authorities: [{ __kind: "Owner" }],
plugin: {
__kind: 'Freeze',
fields: [{ frozen: false }],
},
}],
});
});
1 change: 1 addition & 0 deletions programs/mpl-asset/src/plugins/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ pub fn delete_plugin<'a>(
.ok_or(MplAssetError::NumericalOverflow)?;
solana_program::msg!("data_to_move: {:?}", data_to_move);

//TODO: This is memory intensive, we should use memmove instead probably.
let src = account.data.borrow()[next_plugin_offset..].to_vec();
solana_program::msg!("src: {:?}", src);
sol_memcpy(
Expand Down
102 changes: 92 additions & 10 deletions programs/mpl-asset/src/processor/update.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use borsh::{BorshDeserialize, BorshSerialize};
use mpl_utils::assert_signer;
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
use mpl_utils::{assert_signer, resize_or_reallocate_account_raw};
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, program_memory::sol_memcpy,
};

use crate::{
error::MplAssetError,
instruction::accounts::UpdateAccounts,
plugins::{CheckResult, Plugin, ValidationResult},
state::{Asset, SolanaAccount},
plugins::{CheckResult, Plugin, RegistryData, RegistryRecord, ValidationResult},
state::{Asset, DataBlob, SolanaAccount},
utils::fetch_core_data,
};

Expand All @@ -23,11 +25,17 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P

// Guards.
assert_signer(ctx.accounts.authority)?;
if let Some(payer) = ctx.accounts.payer {
assert_signer(payer)?;
}
let payer = match ctx.accounts.payer {
Some(payer) => {
assert_signer(payer)?;
payer
}
None => ctx.accounts.authority,
};

let (mut asset, _, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?;
let (mut asset, plugin_header, plugin_registry) = fetch_core_data(ctx.accounts.asset_address)?;
let asset_size = asset.get_size() as isize;
solana_program::msg!("asset_size: {:?}", asset_size);

let mut approved = false;
match Asset::check_update() {
Expand All @@ -40,7 +48,7 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P
CheckResult::None => (),
};

if let Some(plugin_registry) = plugin_registry {
if let Some(plugin_registry) = plugin_registry.clone() {
for record in plugin_registry.registry {
if matches!(
record.plugin_type.check_transfer(),
Expand Down Expand Up @@ -75,7 +83,81 @@ pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> P
dirty = true;
}
if dirty {
//TODO: Handle resize!
if let (Some(mut plugin_header), Some(mut plugin_registry)) =
(plugin_header, plugin_registry)
{
let new_asset_size = asset.get_size() as isize;
solana_program::msg!("new_asset_size: {:?}", new_asset_size);
let size_diff = new_asset_size
.checked_sub(asset_size)
.ok_or(MplAssetError::NumericalOverflow)?;
let new_size = (ctx.accounts.asset_address.data_len() as isize)
.checked_add(size_diff)
.ok_or(MplAssetError::NumericalOverflow)?;
solana_program::msg!("size_diff: {:?}", size_diff);
solana_program::msg!(
"old plugin_registry_offset: {:?}",
plugin_header.plugin_registry_offset
);
let new_registry_offset = (plugin_header.plugin_registry_offset as isize)
.checked_add(size_diff)
.ok_or(MplAssetError::NumericalOverflow)?;
solana_program::msg!("new_registry_offset: {:?}", new_registry_offset);
let registry_offset = plugin_header.plugin_registry_offset;
plugin_header.plugin_registry_offset = new_registry_offset as usize;

let plugin_offset = asset_size
.checked_add(size_diff)
.ok_or(MplAssetError::NumericalOverflow)?;
let new_plugin_offset = new_asset_size
.checked_add(size_diff)
.ok_or(MplAssetError::NumericalOverflow)?;

// //TODO: This is memory intensive, we should use memmove instead probably.
let src = ctx.accounts.asset_address.data.borrow()
[(plugin_offset as usize)..registry_offset]
.to_vec();

resize_or_reallocate_account_raw(
ctx.accounts.asset_address,
payer,
ctx.accounts.system_program,
new_size as usize,
)?;

sol_memcpy(
&mut ctx.accounts.asset_address.data.borrow_mut()[(new_plugin_offset as usize)..],
&src,
src.len(),
);

plugin_header.save(ctx.accounts.asset_address, new_asset_size as usize)?;
plugin_registry.registry = plugin_registry
.registry
.iter_mut()
.map(|record| {
let new_offset = (record.data.offset as isize)
.checked_add(size_diff)
.ok_or(MplAssetError::NumericalOverflow)?;
Ok(RegistryRecord {
plugin_type: record.plugin_type,
data: RegistryData {
offset: new_offset as usize,
authorities: record.data.authorities.clone(),
},
})
})
.collect::<Result<Vec<_>, MplAssetError>>()?;
plugin_registry.save(ctx.accounts.asset_address, new_registry_offset as usize)?;
} else {
resize_or_reallocate_account_raw(
ctx.accounts.asset_address,
payer,
ctx.accounts.system_program,
asset.get_size(),
)?;
}

asset.save(ctx.accounts.asset_address, 0)?;
}

Expand Down

0 comments on commit cf1388b

Please sign in to comment.