From 95ddb7b53c72ee4587e58e4a1627d5b8bb760a8e Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sat, 17 Feb 2024 13:06:24 -0800 Subject: [PATCH 01/20] Create instruction processor stubs --- programs/mpl-asset/src/instruction.rs | 70 ++----------------- programs/mpl-asset/src/processor/burn.rs | 26 +++++++ programs/mpl-asset/src/processor/compress.rs | 27 +++++++ programs/mpl-asset/src/processor/create.rs | 12 +++- .../mpl-asset/src/processor/decompress.rs | 30 ++++++++ programs/mpl-asset/src/processor/delegate.rs | 27 +++++++ programs/mpl-asset/src/processor/freeze.rs | 27 +++++++ programs/mpl-asset/src/processor/migrate.rs | 30 ++++++++ programs/mpl-asset/src/processor/mod.rs | 42 +++++++++-- programs/mpl-asset/src/processor/thaw.rs | 27 +++++++ programs/mpl-asset/src/processor/transfer.rs | 27 +++++++ programs/mpl-asset/src/processor/update.rs | 30 ++++++++ programs/mpl-asset/src/state/collection.rs | 7 +- 13 files changed, 304 insertions(+), 78 deletions(-) create mode 100644 programs/mpl-asset/src/processor/burn.rs create mode 100644 programs/mpl-asset/src/processor/compress.rs create mode 100644 programs/mpl-asset/src/processor/decompress.rs create mode 100644 programs/mpl-asset/src/processor/delegate.rs create mode 100644 programs/mpl-asset/src/processor/freeze.rs create mode 100644 programs/mpl-asset/src/processor/migrate.rs create mode 100644 programs/mpl-asset/src/processor/thaw.rs create mode 100644 programs/mpl-asset/src/processor/transfer.rs create mode 100644 programs/mpl-asset/src/processor/update.rs diff --git a/programs/mpl-asset/src/instruction.rs b/programs/mpl-asset/src/instruction.rs index ef4ece8b..355c742d 100644 --- a/programs/mpl-asset/src/instruction.rs +++ b/programs/mpl-asset/src/instruction.rs @@ -1,7 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::{ShankContext, ShankInstruction}; -use crate::state::DataState; +use crate::processor::{ + BurnArgs, CompressArgs, CreateArgs, DecompressArgs, DelegateArgs, FreezeArgs, MigrateArgs, + ThawArgs, TransferArgs, UpdateArgs, +}; #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, ShankContext, ShankInstruction)] #[rustfmt::skip] @@ -110,68 +113,3 @@ pub enum MplAssetInstruction { #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] Decompress(DecompressArgs), } - -//TODO: Implement this struct -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct CompressionProof {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct CreateArgs { - pub data_state: DataState, - pub name: String, - pub uri: String, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct MigrateArgs { - pub data_state: DataState, - pub level: MigrationLevel, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct DelegateArgs {} - -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct BurnArgs {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct TransferArgs {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct UpdateArgs { - pub new_name: String, - pub new_uri: String, -} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct SetFreezeAuthorityArgs {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct FreezeArgs {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct ThawArgs {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct CompressArgs {} - -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct DecompressArgs {} diff --git a/programs/mpl-asset/src/processor/burn.rs b/programs/mpl-asset/src/processor/burn.rs new file mode 100644 index 00000000..f3e8eca7 --- /dev/null +++ b/programs/mpl-asset/src/processor/burn.rs @@ -0,0 +1,26 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct BurnArgs {} + +pub(crate) fn burn<'a>(accounts: &'a [AccountInfo<'a>], args: BurnArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/compress.rs b/programs/mpl-asset/src/processor/compress.rs new file mode 100644 index 00000000..d424241c --- /dev/null +++ b/programs/mpl-asset/src/processor/compress.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct CompressArgs {} + +pub(crate) fn compress<'a>(accounts: &'a [AccountInfo<'a>], args: CompressArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/create.rs b/programs/mpl-asset/src/processor/create.rs index d3d312e2..f5c3d5b6 100644 --- a/programs/mpl-asset/src/processor/create.rs +++ b/programs/mpl-asset/src/processor/create.rs @@ -1,4 +1,4 @@ -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, @@ -7,10 +7,18 @@ use solana_program::{ use crate::{ error::MplAssetError, - instruction::{accounts::CreateAccounts, CreateArgs}, + instruction::accounts::CreateAccounts, state::{Asset, Compressible, DataState, HashedAsset, Key}, }; +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub(crate) struct CreateArgs { + pub data_state: DataState, + pub name: String, + pub uri: String, +} + pub(crate) fn create<'a>(accounts: &'a [AccountInfo<'a>], args: CreateArgs) -> ProgramResult { // Accounts. let ctx = CreateAccounts::context(accounts)?; diff --git a/programs/mpl-asset/src/processor/decompress.rs b/programs/mpl-asset/src/processor/decompress.rs new file mode 100644 index 00000000..92f90ad9 --- /dev/null +++ b/programs/mpl-asset/src/processor/decompress.rs @@ -0,0 +1,30 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct DecompressArgs {} + +pub(crate) fn decompress<'a>( + accounts: &'a [AccountInfo<'a>], + args: DecompressArgs, +) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/delegate.rs b/programs/mpl-asset/src/processor/delegate.rs new file mode 100644 index 00000000..071e7884 --- /dev/null +++ b/programs/mpl-asset/src/processor/delegate.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct DelegateArgs {} + +pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], args: DelegateArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/freeze.rs b/programs/mpl-asset/src/processor/freeze.rs new file mode 100644 index 00000000..b8f41a27 --- /dev/null +++ b/programs/mpl-asset/src/processor/freeze.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct FreezeArgs {} + +pub(crate) fn freeze<'a>(accounts: &'a [AccountInfo<'a>], args: FreezeArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/migrate.rs b/programs/mpl-asset/src/processor/migrate.rs new file mode 100644 index 00000000..8d09e8be --- /dev/null +++ b/programs/mpl-asset/src/processor/migrate.rs @@ -0,0 +1,30 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct MigrateArgs { + pub data_state: DataState, + pub level: MigrationLevel, +} + +pub(crate) fn migrate<'a>(accounts: &'a [AccountInfo<'a>], args: MigrateArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/mod.rs b/programs/mpl-asset/src/processor/mod.rs index 9abce78a..9ff2feea 100644 --- a/programs/mpl-asset/src/processor/mod.rs +++ b/programs/mpl-asset/src/processor/mod.rs @@ -1,14 +1,44 @@ -mod create; - -use create::*; - -use borsh::BorshDeserialize; +use crate::instruction::MplAssetInstruction; +use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, }; -use crate::instruction::MplAssetInstruction; +mod create; +pub(crate) use create::*; + +mod migrate; +pub(crate) use migrate::*; + +mod delegate; +pub(crate) use delegate::*; + +mod burn; +pub(crate) use burn::*; + +mod transfer; +pub(crate) use transfer::*; + +mod update; +pub(crate) use update::*; + +mod freeze; +pub(crate) use freeze::*; + +mod thaw; +pub(crate) use thaw::*; + +mod compress; +pub(crate) use compress::*; + +mod decompress; +pub(crate) use decompress::*; + +//TODO: Implement this struct +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct CompressionProof {} pub fn process_instruction<'a>( _program_id: &Pubkey, diff --git a/programs/mpl-asset/src/processor/thaw.rs b/programs/mpl-asset/src/processor/thaw.rs new file mode 100644 index 00000000..235dd0db --- /dev/null +++ b/programs/mpl-asset/src/processor/thaw.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct ThawArgs {} + +pub(crate) fn thaw<'a>(accounts: &'a [AccountInfo<'a>], args: ThawArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs new file mode 100644 index 00000000..1a7edcf6 --- /dev/null +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -0,0 +1,27 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct TransferArgs {} + +pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/processor/update.rs b/programs/mpl-asset/src/processor/update.rs new file mode 100644 index 00000000..8f223a17 --- /dev/null +++ b/programs/mpl-asset/src/processor/update.rs @@ -0,0 +1,30 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_utils::assert_signer; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, +}; + +use crate::{ + error::MplAssetError, + instruction::accounts::CreateAccounts, + state::{Asset, Compressible, DataState, HashedAsset, Key}, +}; + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct UpdateArgs { + pub new_name: String, + pub new_uri: String, +} + +pub(crate) fn update<'a>(accounts: &'a [AccountInfo<'a>], args: UpdateArgs) -> ProgramResult { + Ok(()) +} diff --git a/programs/mpl-asset/src/state/collection.rs b/programs/mpl-asset/src/state/collection.rs index 1348e7fa..781792aa 100644 --- a/programs/mpl-asset/src/state/collection.rs +++ b/programs/mpl-asset/src/state/collection.rs @@ -8,8 +8,7 @@ pub struct CollectionData { pub owner: Pubkey, //32 pub name: String, //4 pub uri: String, //4 - // TBD creator stats: - pub num_minted: u64, - pub num_migrated: u64, - pub current_size: u64, + pub num_minted: u64, //8 + pub num_migrated: u64, //8 + pub current_size: u64, //8 } From 60f4a413e27f9596accca0835cae83b73ac7ff82 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sat, 17 Feb 2024 13:08:25 -0800 Subject: [PATCH 02/20] WIP transfer --- programs/mpl-asset/src/instruction.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/programs/mpl-asset/src/instruction.rs b/programs/mpl-asset/src/instruction.rs index 355c742d..7a02274b 100644 --- a/programs/mpl-asset/src/instruction.rs +++ b/programs/mpl-asset/src/instruction.rs @@ -59,8 +59,7 @@ pub enum MplAssetInstruction { #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] Burn(BurnArgs), - //TODO: Implement this instruction - /// Transfer an mpl-asset. + // danenbm working on #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] From d11905c3bdbb3221c442c856b9e1e9acec73a92a Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 14:26:45 -0700 Subject: [PATCH 03/20] Adding DataBlob trait. --- programs/mpl-asset/src/utils.rs | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 programs/mpl-asset/src/utils.rs diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs new file mode 100644 index 00000000..e829d81a --- /dev/null +++ b/programs/mpl-asset/src/utils.rs @@ -0,0 +1,4 @@ +pub trait DataBlob { + fn get_initial_size() -> usize; + fn get_size(&self) -> usize; +} From 1abee8a241dd44de88c938a50e37cb12c8529fc1 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 14:28:02 -0700 Subject: [PATCH 04/20] WIP utils. --- programs/mpl-asset/src/error.rs | 10 +++ programs/mpl-asset/src/lib.rs | 1 + programs/mpl-asset/src/plugins/mod.rs | 36 +++++++-- programs/mpl-asset/src/plugins/utils.rs | 77 +++++++++++++++++-- programs/mpl-asset/src/processor/create.rs | 2 +- programs/mpl-asset/src/state/asset.rs | 16 ++++ programs/mpl-asset/src/state/plugin_header.rs | 23 ++++-- 7 files changed, 142 insertions(+), 23 deletions(-) diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index 05679b12..b0e48b35 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -11,12 +11,22 @@ pub enum MplAssetError { /// 0 - Invalid System Program #[error("Invalid System Program")] InvalidSystemProgram, + /// 1 - Error deserializing account #[error("Error deserializing account")] DeserializationError, + /// 2 - Error serializing account #[error("Error serializing account")] SerializationError, + + /// 3 - Plugins not initialized + #[error("Plugins not initialized")] + PluginsNotInitialized, + + /// 4 - Plugin not found + #[error("Plugin not found")] + PluginNotFound, } impl PrintProgramError for MplAssetError { diff --git a/programs/mpl-asset/src/lib.rs b/programs/mpl-asset/src/lib.rs index 42695316..c3b46a5c 100644 --- a/programs/mpl-asset/src/lib.rs +++ b/programs/mpl-asset/src/lib.rs @@ -4,6 +4,7 @@ pub mod instruction; pub mod plugins; pub mod processor; pub mod state; +pub mod utils; pub use solana_program; diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index c7b7a58d..51ba983b 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -4,12 +4,17 @@ mod utils; pub use collection::*; pub use royalties::*; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; pub use utils::*; use borsh::{BorshDeserialize, BorshSerialize}; -use std::collections::HashMap; -use crate::state::Authority; +use crate::{ + error::MplAssetError, + state::{Authority, Key}, +}; // macro_rules! plugin_instruction { // ($a:expr, $b:expr) => { @@ -35,20 +40,35 @@ pub enum Plugin { // Burn = plugin_instruction!(Plugin::Metadata, 2), //} +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct RegistryData { pub offset: usize, pub authorities: Vec, } +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct PluginRegistry { - pub registry: HashMap, + pub registry: Vec<(Key, RegistryData)>, + // pub third_party_registry: HashMap, } -pub trait DataStorage { - fn get_required_length(&self); - fn save(&self, data: &mut [u8]); - fn load(&self, data: &[u8]); - fn load_mut(&self, data: &mut [u8]); +impl PluginRegistry { + pub fn load(account: &AccountInfo, offset: usize) -> Result { + let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; + PluginRegistry::deserialize(&mut bytes).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::DeserializationError.into() + }) + } + + pub fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { + borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::SerializationError.into() + }) + } } // pub trait PluginTrait diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 0ad36d63..23bc8217 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -1,16 +1,77 @@ -use super::Plugin; +use borsh::BorshDeserialize; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; -//TODO: Implement this function -pub fn create_idempotent() { - // Create plugin header and registry if it doesn't exist +use crate::{ + error::MplAssetError, + state::{Asset, Key, PluginHeader}, + utils::DataBlob, +}; + +use super::{Plugin, PluginRegistry}; + +//TODO:keith: Implement this function +/// Create plugin header and registry if it doesn't exist +pub fn create_idempotent(account: &AccountInfo) -> ProgramResult { + let mut bytes: &[u8] = &(*account.data).borrow(); + let asset = Asset::deserialize(&mut bytes)?; + + // Check if the plugin header and registry exist. + if asset.get_size() == account.data_len() { + // They don't exist, so create them. + let header = PluginHeader { + version: 1, + plugin_map_offset: asset.get_size() + PluginHeader::get_initial_size(), + }; + let registry = PluginRegistry { registry: vec![] }; + + header.save(account, asset.get_size())?; + registry.save(account, header.plugin_map_offset)?; + } + + Ok(()) } -//TODO: Implement this function -pub fn fetch_plugin(plugin: u8) -> Plugin { - // Create plugin header and registry if it doesn't exist +pub fn assert_plugins_initialized(account: &AccountInfo) -> ProgramResult { + let mut bytes: &[u8] = &(*account.data).borrow(); + let asset = Asset::deserialize(&mut bytes).unwrap(); + + if asset.get_size() == account.data_len() { + return Err(MplAssetError::PluginsNotInitialized.into()); + } + + Ok(()) +} + +//TODO:keith: Implement this function +pub fn fetch_plugin( + account: &AccountInfo, + plugin: Key, +) -> Result<((Vec, Plugin)), ProgramError> { + // Fetch the plugin from the registry. + let mut bytes: &[u8] = &(*account.data).borrow(); + let asset = Asset::deserialize(&mut bytes)?; + + let header = PluginHeader::load(account, asset.get_size())?; + let PluginRegistry { registry } = PluginRegistry::load(account, header.plugin_map_offset)?; + + let plugin_data = registry + .iter() + .find(|(key, _)| *key == plugin) + .map(|(_, data)| data) + .ok_or(MplAssetError::PluginNotFound)?; + + let authorities = plugin_data + .authorities + .iter() + .map(|authority| authority.key) + .collect(); } -//TODO: Implement this function +//TODO:keith: Implement this function pub fn list_plugins() -> Vec { // Create plugin header and registry if it doesn't exist + vec![] } diff --git a/programs/mpl-asset/src/processor/create.rs b/programs/mpl-asset/src/processor/create.rs index f5c3d5b6..d0c641b7 100644 --- a/programs/mpl-asset/src/processor/create.rs +++ b/programs/mpl-asset/src/processor/create.rs @@ -13,7 +13,7 @@ use crate::{ #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub(crate) struct CreateArgs { +pub struct CreateArgs { pub data_state: DataState, pub name: String, pub uri: String, diff --git a/programs/mpl-asset/src/state/asset.rs b/programs/mpl-asset/src/state/asset.rs index b2ea03fa..ef9213ac 100644 --- a/programs/mpl-asset/src/state/asset.rs +++ b/programs/mpl-asset/src/state/asset.rs @@ -2,6 +2,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; use solana_program::{keccak, program_error::ProgramError, pubkey::Pubkey}; +use crate::utils::DataBlob; + use super::{Compressible, Key}; #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] @@ -13,6 +15,10 @@ pub struct Asset { pub uri: String, //4 } +impl Asset { + pub const BASE_LENGTH: usize = 1 + 32 + 32 + 4 + 4; +} + impl Compressible for Asset { fn hash(&self) -> Result<[u8; 32], ProgramError> { let serialized_data = self.try_to_vec()?; @@ -21,6 +27,16 @@ impl Compressible for Asset { } } +impl DataBlob for Asset { + fn get_initial_size() -> usize { + Asset::BASE_LENGTH + } + + fn get_size(&self) -> usize { + Asset::BASE_LENGTH + self.name.len() + self.uri.len() + } +} + #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct HashedAsset { pub key: Key, //1 diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index 559ab856..22596c1d 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -5,27 +5,38 @@ use solana_program::msg; use solana_program::program_error::ProgramError; use crate::error::MplAssetError; +use crate::utils::DataBlob; #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct PluginHeader { - pub version: u8, - pub plugin_map_offset: usize, + pub version: u8, // 1 + pub plugin_map_offset: usize, // 8 } impl PluginHeader { - pub fn load(account: &AccountInfo) -> Result { - let mut bytes: &[u8] = &(*account.data).borrow(); + pub fn load(account: &AccountInfo, offset: usize) -> Result { + let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; PluginHeader::deserialize(&mut bytes).map_err(|error| { msg!("Error: {}", error); MplAssetError::DeserializationError.into() }) } - pub fn save(&self, account: &AccountInfo) -> ProgramResult { - borsh::to_writer(&mut account.data.borrow_mut()[..], self).map_err(|error| { + pub fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { + borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { msg!("Error: {}", error); MplAssetError::SerializationError.into() }) } } + +impl DataBlob for PluginHeader { + fn get_initial_size() -> usize { + 1 + 8 + } + + fn get_size(&self) -> usize { + 1 + 8 + } +} From 939a636da523824cd2b83437d974e5a7276dd82a Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 14:38:53 -0700 Subject: [PATCH 05/20] Filling out processor match statement. --- programs/mpl-asset/src/processor/mod.rs | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/programs/mpl-asset/src/processor/mod.rs b/programs/mpl-asset/src/processor/mod.rs index 9ff2feea..b2d5f831 100644 --- a/programs/mpl-asset/src/processor/mod.rs +++ b/programs/mpl-asset/src/processor/mod.rs @@ -51,6 +51,42 @@ pub fn process_instruction<'a>( msg!("Instruction: Create"); create(accounts, args) } + MplAssetInstruction::Migrate(args) => { + msg!("Instruction: Migrate"); + migrate(accounts, args) + } + MplAssetInstruction::Delegate(args) => { + msg!("Instruction: Delegate"); + delegate(accounts, args) + } + MplAssetInstruction::Burn(args) => { + msg!("Instruction: Burn"); + burn(accounts, args) + } + MplAssetInstruction::Transfer(args) => { + msg!("Instruction: Transfer"); + transfer(accounts, args) + } + MplAssetInstruction::Update(args) => { + msg!("Instruction: Update"); + update(accounts, args) + } + MplAssetInstruction::Freeze(args) => { + msg!("Instruction: Freeze"); + freeze(accounts, args) + } + MplAssetInstruction::Thaw(args) => { + msg!("Instruction: Thaw"); + thaw(accounts, args) + } + MplAssetInstruction::Compress(args) => { + msg!("Instruction: Compress"); + compress(accounts, args) + } + MplAssetInstruction::Decompress(args) => { + msg!("Instruction: Decompress"); + decompress(accounts, args) + } _ => Err(ProgramError::InvalidInstructionData), } } From 112017b9629afba2f8197e11ee458af1e7bce567 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 14:39:37 -0700 Subject: [PATCH 06/20] Removing warnings. --- programs/mpl-asset/src/processor/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/programs/mpl-asset/src/processor/mod.rs b/programs/mpl-asset/src/processor/mod.rs index b2d5f831..ddf53a27 100644 --- a/programs/mpl-asset/src/processor/mod.rs +++ b/programs/mpl-asset/src/processor/mod.rs @@ -1,9 +1,6 @@ use crate::instruction::MplAssetInstruction; use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, - pubkey::Pubkey, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; mod create; pub(crate) use create::*; @@ -87,6 +84,5 @@ pub fn process_instruction<'a>( msg!("Instruction: Decompress"); decompress(accounts, args) } - _ => Err(ProgramError::InvalidInstructionData), } } From 0c3ebb5ddfd121b22b5116dc1871c8a44a38e54e Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sat, 17 Feb 2024 16:59:31 -0800 Subject: [PATCH 07/20] Basic transfer implementation --- programs/mpl-asset/src/error.rs | 8 ++ programs/mpl-asset/src/instruction.rs | 3 +- programs/mpl-asset/src/processor/mod.rs | 5 - programs/mpl-asset/src/processor/transfer.rs | 114 ++++++------------ programs/mpl-asset/src/state/asset.rs | 19 ++- programs/mpl-asset/src/state/hashed_asset.rs | 35 ++++++ programs/mpl-asset/src/state/mod.rs | 14 +++ programs/mpl-asset/src/state/plugin_header.rs | 7 +- programs/mpl-asset/src/utils.rs | 10 +- 9 files changed, 119 insertions(+), 96 deletions(-) create mode 100644 programs/mpl-asset/src/state/hashed_asset.rs diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index b0e48b35..e9fd26f9 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -27,6 +27,14 @@ pub enum MplAssetError { /// 4 - Plugin not found #[error("Plugin not found")] PluginNotFound, + + /// 5 - Incorrect account + #[error("Incorrect account")] + IncorrectAccount, + + /// 5 - Provided data does not match asset hash. + #[error("Incorrect asset hash")] + IncorrectAssetHash, } impl PrintProgramError for MplAssetError { diff --git a/programs/mpl-asset/src/instruction.rs b/programs/mpl-asset/src/instruction.rs index 646838ac..38e49791 100644 --- a/programs/mpl-asset/src/instruction.rs +++ b/programs/mpl-asset/src/instruction.rs @@ -60,7 +60,8 @@ pub enum MplAssetInstruction { #[account(4, optional, name="log_wrapper", desc = "The SPL Noop Program")] Burn(BurnArgs), - // danenbm working on + // Transfer an asset. + // danenbm WIP #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] diff --git a/programs/mpl-asset/src/processor/mod.rs b/programs/mpl-asset/src/processor/mod.rs index ddf53a27..2c956da9 100644 --- a/programs/mpl-asset/src/processor/mod.rs +++ b/programs/mpl-asset/src/processor/mod.rs @@ -32,11 +32,6 @@ pub(crate) use compress::*; mod decompress; pub(crate) use decompress::*; -//TODO: Implement this struct -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct CompressionProof {} - pub fn process_instruction<'a>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'a>], diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 83aada67..9d4562e0 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -2,28 +2,21 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, system_instruction, system_program, + program_memory::sol_memcpy, }; use crate::{ error::MplAssetError, instruction::accounts::TransferAccounts, - state::{Asset, Compressible, DataState, HashedAsset, Key}, - utils::DataBlob, + state::{Asset, Compressible, CompressionProof, HashedAsset, Key}, + utils::{load_key, DataBlob}, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub struct TransferArgs {} - -// // danenbm working on -// #[account(0, writable, name="asset_address", desc = "The address of the asset")] -// #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] -// #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] -// #[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")] -// #[account(4, name="new_owner", desc = "The new owner to which to transfer the asset")] -// #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] -// Transfer(TransferArgs), +pub struct TransferArgs { + compression_proof: CompressionProof, +} pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) -> ProgramResult { // Accounts. @@ -31,74 +24,41 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) // Guards. assert_signer(ctx.accounts.authority)?; - if let Some(payer) = ctx.accounts.payer { assert_signer(payer)?; } - let asset = Asset::load(ctx.accounts.asset_address, 0)?; - - asset.owner = ctx.accounts.new_owner.key(); - - // if *ctx.accounts.system_program.key != system_program::id() { - // return Err(MplAssetError::InvalidSystemProgram.into()); - // } - - // let updated_asset = Asset { - // key: Key::Asset, - // update_authority: *ctx - // .accounts - // .update_authority - // .unwrap_or(ctx.accounts.payer) - // .key, - // owner: *ctx - // .accounts - // .owner - // .unwrap_or(ctx.accounts.update_authority.unwrap_or(ctx.accounts.payer)) - // .key, - // name: args.name, - // uri: args.uri, - // }; - - // let serialized_data = new_asset.try_to_vec()?; - - // let serialized_data = match args.data_state { - // DataState::AccountState => serialized_data, - // DataState::LedgerState => { - // invoke(&spl_noop::instruction(serialized_data.clone()), &[])?; - - // let hashed_asset = HashedAsset { - // key: Key::HashedAsset, - // hash: new_asset.hash()?, - // }; - - // hashed_asset.try_to_vec()? - // } - // }; - - //let lamports = rent.minimum_balance(serialized_data.len()); - - // CPI to the System Program. - // invoke( - // &system_instruction::create_account( - // ctx.accounts.payer.key, - // ctx.accounts.asset_address.key, - // lamports, - // serialized_data.len() as u64, - // &crate::id(), - // ), - // &[ - // ctx.accounts.payer.clone(), - // ctx.accounts.asset_address.clone(), - // ctx.accounts.system_program.clone(), - // ], - // )?; - - // sol_memcpy( - // &mut ctx.accounts.asset_address.try_borrow_mut_data()?, - // &serialized_data, - // serialized_data.len(), - // ); + let serialized_data = match load_key(ctx.accounts.asset_address, 0)? { + Key::HashedAsset => { + // Check that arguments passed in result in on-chain hash. + let mut asset = Asset::from(args.compression_proof); + let args_asset_hash = asset.hash()?; + let current_account_hash = HashedAsset::load(ctx.accounts.asset_address, 0)?.hash; + if args_asset_hash != current_account_hash { + return Err(MplAssetError::IncorrectAssetHash.into()); + } + + // Update owner and send Noop instruction. + asset.owner = *ctx.accounts.new_owner.key; + let serialized_data = asset.try_to_vec()?; + invoke(&spl_noop::instruction(serialized_data), &[])?; + + // Make a new hashed asset with updated owner. + HashedAsset::new(asset.hash()?).try_to_vec()? + } + Key::Asset => { + let mut asset = Asset::load(ctx.accounts.asset_address, 0)?; + asset.owner = *ctx.accounts.new_owner.key; + asset.try_to_vec()? + } + _ => return Err(MplAssetError::IncorrectAccount.into()), + }; + + sol_memcpy( + &mut ctx.accounts.asset_address.try_borrow_mut_data()?, + &serialized_data, + serialized_data.len(), + ); Ok(()) } diff --git a/programs/mpl-asset/src/state/asset.rs b/programs/mpl-asset/src/state/asset.rs index 5424ec96..c67fc0aa 100644 --- a/programs/mpl-asset/src/state/asset.rs +++ b/programs/mpl-asset/src/state/asset.rs @@ -2,7 +2,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; use solana_program::{keccak, program_error::ProgramError, pubkey::Pubkey}; -use crate::{state::Key, utils::DataBlob}; +use crate::{ + state::{CompressionProof, Key}, + utils::DataBlob, +}; use super::Compressible; @@ -41,8 +44,14 @@ impl DataBlob for Asset { } } -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] -pub struct HashedAsset { - pub key: Key, //1 - pub hash: [u8; 32], //32 +impl From for Asset { + fn from(compression_proof: CompressionProof) -> Self { + Self { + key: Self::key(), + update_authority: compression_proof.update_authority, + owner: compression_proof.owner, + name: compression_proof.name, + uri: compression_proof.uri, + } + } } diff --git a/programs/mpl-asset/src/state/hashed_asset.rs b/programs/mpl-asset/src/state/hashed_asset.rs new file mode 100644 index 00000000..0c3931b8 --- /dev/null +++ b/programs/mpl-asset/src/state/hashed_asset.rs @@ -0,0 +1,35 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use shank::ShankAccount; + +use crate::{state::Key, utils::DataBlob}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount, PartialEq, Eq)] +pub struct HashedAsset { + pub key: Key, //1 + pub hash: [u8; 32], //32 +} + +impl HashedAsset { + pub const LENGTH: usize = 1 + 32; + + pub fn new(hash: [u8; 32]) -> Self { + Self { + key: Key::HashedAsset, + hash, + } + } +} + +impl DataBlob for HashedAsset { + fn get_initial_size() -> usize { + HashedAsset::LENGTH + } + + fn get_size(&self) -> usize { + HashedAsset::LENGTH + } + + fn key() -> Key { + Key::HashedAsset + } +} diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 87d8ae55..fc2c5d40 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -1,6 +1,9 @@ mod asset; pub use asset::*; +mod hashed_asset; +pub use hashed_asset::*; + mod plugin_header; use num_derive::FromPrimitive; pub use plugin_header::*; @@ -61,3 +64,14 @@ pub enum MigrationLevel { MigrateOnly, MigrateAndBurn, } + +//TODO: Implement this struct +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub struct CompressionProof { + pub key: Key, //1 + pub update_authority: Pubkey, //32 + pub owner: Pubkey, //32 + pub name: String, //4 + pub uri: String, //4 +} diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index efe97dbb..e068f8dc 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -1,10 +1,5 @@ +use crate::{state::Key, utils::DataBlob}; use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; -use solana_program::msg; -use solana_program::program_error::ProgramError; - -use crate::{error::MplAssetError, state::Key, utils::DataBlob}; #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs index a6b57a30..7df899c1 100644 --- a/programs/mpl-asset/src/utils.rs +++ b/programs/mpl-asset/src/utils.rs @@ -12,8 +12,7 @@ pub trait DataBlob: BorshSerialize + BorshDeserialize { fn key() -> Key; fn load(account: &AccountInfo, offset: usize) -> Result { - let key = Key::from_u8((*account.data).borrow()[offset]) - .ok_or(MplAssetError::DeserializationError)?; + let key = load_key(account, offset)?; if key != Self::key() { return Err(MplAssetError::DeserializationError.into()); @@ -33,3 +32,10 @@ pub trait DataBlob: BorshSerialize + BorshDeserialize { }) } } + +pub fn load_key(account: &AccountInfo, offset: usize) -> Result { + let key = Key::from_u8((*account.data).borrow()[offset]) + .ok_or(MplAssetError::DeserializationError)?; + + Ok(key) +} From 77222e62ef55e9ebd36ef5313e9d68900d51fbf0 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sat, 17 Feb 2024 14:16:03 -0800 Subject: [PATCH 08/20] Move load and save to trait and check key --- programs/mpl-asset/src/processor/transfer.rs | 88 ++++++++++++++++++- programs/mpl-asset/src/state/asset.rs | 8 +- programs/mpl-asset/src/state/mod.rs | 4 +- programs/mpl-asset/src/state/plugin_header.rs | 24 ++--- programs/mpl-asset/src/utils.rs | 33 ++++++- 5 files changed, 132 insertions(+), 25 deletions(-) diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 1a7edcf6..684a7132 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -2,13 +2,14 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, + program_memory::sol_memcpy, system_instruction, system_program, }; use crate::{ error::MplAssetError, - instruction::accounts::CreateAccounts, + instruction::accounts::TransferAccounts, state::{Asset, Compressible, DataState, HashedAsset, Key}, + utils::DataBlob, }; #[repr(C)] @@ -22,6 +23,89 @@ pub enum MigrationLevel { #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct TransferArgs {} +// // danenbm working on +// #[account(0, writable, name="asset_address", desc = "The address of the asset")] +// #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] +// #[account(2, signer, name="authority", desc = "The owner or delegate of the asset")] +// #[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")] +// #[account(4, name="new_owner", desc = "The new owner to which to transfer the asset")] +// #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] +// Transfer(TransferArgs), + pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) -> ProgramResult { + // Accounts. + let ctx = TransferAccounts::context(accounts)?; + + // Guards. + assert_signer(ctx.accounts.authority)?; + + if let Some(payer) = ctx.accounts.payer { + assert_signer(payer)?; + } + + let asset = Asset::load(ctx.accounts.asset_address, 0)?; + + asset.owner = ctx.accounts.new_owner.key(); + + // if *ctx.accounts.system_program.key != system_program::id() { + // return Err(MplAssetError::InvalidSystemProgram.into()); + // } + + // let updated_asset = Asset { + // key: Key::Asset, + // update_authority: *ctx + // .accounts + // .update_authority + // .unwrap_or(ctx.accounts.payer) + // .key, + // owner: *ctx + // .accounts + // .owner + // .unwrap_or(ctx.accounts.update_authority.unwrap_or(ctx.accounts.payer)) + // .key, + // name: args.name, + // uri: args.uri, + // }; + + // let serialized_data = new_asset.try_to_vec()?; + + // let serialized_data = match args.data_state { + // DataState::AccountState => serialized_data, + // DataState::LedgerState => { + // invoke(&spl_noop::instruction(serialized_data.clone()), &[])?; + + // let hashed_asset = HashedAsset { + // key: Key::HashedAsset, + // hash: new_asset.hash()?, + // }; + + // hashed_asset.try_to_vec()? + // } + // }; + + //let lamports = rent.minimum_balance(serialized_data.len()); + + // CPI to the System Program. + // invoke( + // &system_instruction::create_account( + // ctx.accounts.payer.key, + // ctx.accounts.asset_address.key, + // lamports, + // serialized_data.len() as u64, + // &crate::id(), + // ), + // &[ + // ctx.accounts.payer.clone(), + // ctx.accounts.asset_address.clone(), + // ctx.accounts.system_program.clone(), + // ], + // )?; + + // sol_memcpy( + // &mut ctx.accounts.asset_address.try_borrow_mut_data()?, + // &serialized_data, + // serialized_data.len(), + // ); + Ok(()) } diff --git a/programs/mpl-asset/src/state/asset.rs b/programs/mpl-asset/src/state/asset.rs index ef9213ac..5424ec96 100644 --- a/programs/mpl-asset/src/state/asset.rs +++ b/programs/mpl-asset/src/state/asset.rs @@ -2,9 +2,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; use solana_program::{keccak, program_error::ProgramError, pubkey::Pubkey}; -use crate::utils::DataBlob; +use crate::{state::Key, utils::DataBlob}; -use super::{Compressible, Key}; +use super::Compressible; #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct Asset { @@ -35,6 +35,10 @@ impl DataBlob for Asset { fn get_size(&self) -> usize { Asset::BASE_LENGTH + self.name.len() + self.uri.len() } + + fn key() -> Key { + Key::Asset + } } #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 1b398b07..a0967b73 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -2,6 +2,7 @@ mod asset; pub use asset::*; mod plugin_header; +use num_derive::FromPrimitive; pub use plugin_header::*; use borsh::{BorshDeserialize, BorshSerialize}; @@ -44,11 +45,12 @@ pub trait Compressible { fn hash(&self) -> Result<[u8; 32], ProgramError>; } -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, FromPrimitive)] pub enum Key { Uninitialized, Asset, HashedAsset, Collection, HashedCollection, + PluginHeader, } diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index 22596c1d..efe97dbb 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -4,8 +4,7 @@ use solana_program::entrypoint::ProgramResult; use solana_program::msg; use solana_program::program_error::ProgramError; -use crate::error::MplAssetError; -use crate::utils::DataBlob; +use crate::{error::MplAssetError, state::Key, utils::DataBlob}; #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] @@ -14,23 +13,6 @@ pub struct PluginHeader { pub plugin_map_offset: usize, // 8 } -impl PluginHeader { - pub fn load(account: &AccountInfo, offset: usize) -> Result { - let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; - PluginHeader::deserialize(&mut bytes).map_err(|error| { - msg!("Error: {}", error); - MplAssetError::DeserializationError.into() - }) - } - - pub fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { - borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { - msg!("Error: {}", error); - MplAssetError::SerializationError.into() - }) - } -} - impl DataBlob for PluginHeader { fn get_initial_size() -> usize { 1 + 8 @@ -39,4 +21,8 @@ impl DataBlob for PluginHeader { fn get_size(&self) -> usize { 1 + 8 } + + fn key() -> Key { + Key::PluginHeader + } } diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs index e829d81a..a6b57a30 100644 --- a/programs/mpl-asset/src/utils.rs +++ b/programs/mpl-asset/src/utils.rs @@ -1,4 +1,35 @@ -pub trait DataBlob { +use crate::{error::MplAssetError, state::Key}; +use borsh::{BorshDeserialize, BorshSerialize}; +use num_traits::FromPrimitive; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; +use solana_program::msg; +use solana_program::program_error::ProgramError; + +pub trait DataBlob: BorshSerialize + BorshDeserialize { fn get_initial_size() -> usize; fn get_size(&self) -> usize; + fn key() -> Key; + + fn load(account: &AccountInfo, offset: usize) -> Result { + let key = Key::from_u8((*account.data).borrow()[offset]) + .ok_or(MplAssetError::DeserializationError)?; + + if key != Self::key() { + return Err(MplAssetError::DeserializationError.into()); + } + + let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; + Self::deserialize(&mut bytes).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::DeserializationError.into() + }) + } + + fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { + borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::SerializationError.into() + }) + } } From dcb80c258a9d2bc5963e7e89eac2d49f0ec3da72 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 15:19:26 -0700 Subject: [PATCH 09/20] Plugin registry init. --- .../js/src/generated/accounts/hashedAsset.ts | 28 +- clients/js/src/generated/errors/mplAsset.ts | 26 + clients/js/src/generated/instructions/burn.ts | 114 ++ .../js/src/generated/instructions/compress.ts | 126 +++ .../js/src/generated/instructions/create.ts | 23 +- .../src/generated/instructions/decompress.ts | 126 +++ .../js/src/generated/instructions/delegate.ts | 136 +++ .../js/src/generated/instructions/freeze.ts | 103 ++ .../js/src/generated/instructions/index.ts | 9 + .../js/src/generated/instructions/migrate.ts | 217 ++++ clients/js/src/generated/instructions/thaw.ts | 103 ++ .../js/src/generated/instructions/transfer.ts | 125 ++ .../js/src/generated/instructions/update.ts | 150 +++ clients/js/src/generated/types/authority.ts | 10 +- clients/js/src/generated/types/collection.ts | 32 + .../src/generated/types/compressionProof.ts | 22 + .../js/src/generated/types/extraAccounts.ts | 92 ++ clients/js/src/generated/types/index.ts | 8 +- .../js/src/generated/types/migrationLevel.ts | 25 + clients/js/src/generated/types/plugin.ts | 2 - .../types/{assetHeader.ts => pluginHeader.ts} | 16 +- .../js/src/generated/types/pluginRegistry.ts | 43 + .../js/src/generated/types/registryData.ts | 35 + clients/js/test/create.test.ts | 2 - clients/js/test/delegate.test.ts | 43 + clients/js/test/info.test.ts | 2 - .../src/generated/accounts/hashed_asset.rs | 3 +- .../rust/src/generated/errors/mpl_asset.rs | 6 + .../rust/src/generated/instructions/burn.rs | 477 ++++++++ .../src/generated/instructions/compress.rs | 462 ++++++++ .../rust/src/generated/instructions/create.rs | 124 +- .../src/generated/instructions/decompress.rs | 462 ++++++++ .../src/generated/instructions/delegate.rs | 553 +++++++++ .../rust/src/generated/instructions/freeze.rs | 372 ++++++ .../src/generated/instructions/migrate.rs | 1001 +++++++++++++++++ .../rust/src/generated/instructions/mod.rs | 18 + .../rust/src/generated/instructions/thaw.rs | 372 ++++++ .../src/generated/instructions/transfer.rs | 514 +++++++++ .../rust/src/generated/instructions/update.rs | 586 ++++++++++ clients/rust/src/generated/types/authority.rs | 1 + .../rust/src/generated/types/collection.rs | 21 + .../src/generated/types/compression_proof.rs | 13 + .../src/generated/types/extra_accounts.rs | 28 + .../src/generated/types/migration_level.rs | 16 + clients/rust/src/generated/types/mod.rs | 16 +- clients/rust/src/generated/types/plugin.rs | 2 - .../{asset_header.rs => plugin_header.rs} | 2 +- .../src/generated/types/plugin_registry.rs | 17 + .../rust/src/generated/types/registry_data.rs | 17 + idls/mpl_asset_program.json | 946 +++++++++++++++- programs/mpl-asset/src/instruction.rs | 3 +- programs/mpl-asset/src/plugins/mod.rs | 14 +- programs/mpl-asset/src/plugins/utils.rs | 57 +- programs/mpl-asset/src/processor/burn.rs | 7 - programs/mpl-asset/src/processor/compress.rs | 7 - .../mpl-asset/src/processor/decompress.rs | 7 - programs/mpl-asset/src/processor/delegate.rs | 18 +- programs/mpl-asset/src/processor/freeze.rs | 7 - programs/mpl-asset/src/processor/migrate.rs | 9 +- programs/mpl-asset/src/processor/thaw.rs | 7 - programs/mpl-asset/src/processor/transfer.rs | 7 - programs/mpl-asset/src/processor/update.rs | 7 - programs/mpl-asset/src/state/mod.rs | 9 +- 63 files changed, 7563 insertions(+), 243 deletions(-) create mode 100644 clients/js/src/generated/instructions/burn.ts create mode 100644 clients/js/src/generated/instructions/compress.ts create mode 100644 clients/js/src/generated/instructions/decompress.ts create mode 100644 clients/js/src/generated/instructions/delegate.ts create mode 100644 clients/js/src/generated/instructions/freeze.ts create mode 100644 clients/js/src/generated/instructions/migrate.ts create mode 100644 clients/js/src/generated/instructions/thaw.ts create mode 100644 clients/js/src/generated/instructions/transfer.ts create mode 100644 clients/js/src/generated/instructions/update.ts create mode 100644 clients/js/src/generated/types/collection.ts create mode 100644 clients/js/src/generated/types/compressionProof.ts create mode 100644 clients/js/src/generated/types/extraAccounts.ts create mode 100644 clients/js/src/generated/types/migrationLevel.ts rename clients/js/src/generated/types/{assetHeader.ts => pluginHeader.ts} (59%) create mode 100644 clients/js/src/generated/types/pluginRegistry.ts create mode 100644 clients/js/src/generated/types/registryData.ts create mode 100644 clients/js/test/delegate.test.ts create mode 100644 clients/rust/src/generated/instructions/burn.rs create mode 100644 clients/rust/src/generated/instructions/compress.rs create mode 100644 clients/rust/src/generated/instructions/decompress.rs create mode 100644 clients/rust/src/generated/instructions/delegate.rs create mode 100644 clients/rust/src/generated/instructions/freeze.rs create mode 100644 clients/rust/src/generated/instructions/migrate.rs create mode 100644 clients/rust/src/generated/instructions/thaw.rs create mode 100644 clients/rust/src/generated/instructions/transfer.rs create mode 100644 clients/rust/src/generated/instructions/update.rs create mode 100644 clients/rust/src/generated/types/collection.rs create mode 100644 clients/rust/src/generated/types/compression_proof.rs create mode 100644 clients/rust/src/generated/types/extra_accounts.rs create mode 100644 clients/rust/src/generated/types/migration_level.rs rename clients/rust/src/generated/types/{asset_header.rs => plugin_header.rs} (94%) create mode 100644 clients/rust/src/generated/types/plugin_registry.rs create mode 100644 clients/rust/src/generated/types/registry_data.rs diff --git a/clients/js/src/generated/accounts/hashedAsset.ts b/clients/js/src/generated/accounts/hashedAsset.ts index c7640c2e..deaf9013 100644 --- a/clients/js/src/generated/accounts/hashedAsset.ts +++ b/clients/js/src/generated/accounts/hashedAsset.ts @@ -9,8 +9,6 @@ import { Account, Context, - Option, - OptionOrNullable, Pda, PublicKey, RpcAccount, @@ -24,25 +22,15 @@ import { import { Serializer, bytes, - option, struct, - u64, } from '@metaplex-foundation/umi/serializers'; import { Key, KeyArgs, getKeySerializer } from '../types'; export type HashedAsset = Account; -export type HashedAssetAccountData = { - key: Key; - hash: Uint8Array; - watermarkSlot: Option; -}; +export type HashedAssetAccountData = { key: Key; hash: Uint8Array }; -export type HashedAssetAccountDataArgs = { - key: KeyArgs; - hash: Uint8Array; - watermarkSlot: OptionOrNullable; -}; +export type HashedAssetAccountDataArgs = { key: KeyArgs; hash: Uint8Array }; export function getHashedAssetAccountDataSerializer(): Serializer< HashedAssetAccountDataArgs, @@ -52,7 +40,6 @@ export function getHashedAssetAccountDataSerializer(): Serializer< [ ['key', getKeySerializer()], ['hash', bytes({ size: 32 })], - ['watermarkSlot', option(u64())], ], { description: 'HashedAssetAccountData' } ) as Serializer; @@ -124,16 +111,15 @@ export function getHashedAssetGpaBuilder( 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' ); return gpaBuilder(context, programId) - .registerFields<{ - key: KeyArgs; - hash: Uint8Array; - watermarkSlot: OptionOrNullable; - }>({ + .registerFields<{ key: KeyArgs; hash: Uint8Array }>({ key: [0, getKeySerializer()], hash: [1, bytes({ size: 32 })], - watermarkSlot: [33, option(u64())], }) .deserializeUsing((account) => deserializeHashedAsset(account) ); } + +export function getHashedAssetSize(): number { + return 33; +} diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index 6b2fd77e..ba8e3aa1 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -54,6 +54,32 @@ export class SerializationErrorError extends ProgramError { codeToErrorMap.set(0x2, SerializationErrorError); nameToErrorMap.set('SerializationError', SerializationErrorError); +/** PluginsNotInitialized: Plugins not initialized */ +export class PluginsNotInitializedError extends ProgramError { + readonly name: string = 'PluginsNotInitialized'; + + readonly code: number = 0x3; // 3 + + constructor(program: Program, cause?: Error) { + super('Plugins not initialized', program, cause); + } +} +codeToErrorMap.set(0x3, PluginsNotInitializedError); +nameToErrorMap.set('PluginsNotInitialized', PluginsNotInitializedError); + +/** PluginNotFound: Plugin not found */ +export class PluginNotFoundError extends ProgramError { + readonly name: string = 'PluginNotFound'; + + readonly code: number = 0x4; // 4 + + constructor(program: Program, cause?: Error) { + super('Plugin not found', program, cause); + } +} +codeToErrorMap.set(0x4, PluginNotFoundError); +nameToErrorMap.set('PluginNotFound', PluginNotFoundError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/burn.ts b/clients/js/src/generated/instructions/burn.ts new file mode 100644 index 00000000..58dd5362 --- /dev/null +++ b/clients/js/src/generated/instructions/burn.ts @@ -0,0 +1,114 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type BurnInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type BurnInstructionData = { discriminator: number }; + +export type BurnInstructionDataArgs = {}; + +export function getBurnInstructionDataSerializer(): Serializer< + BurnInstructionDataArgs, + BurnInstructionData +> { + return mapSerializer( + struct([['discriminator', u8()]], { + description: 'BurnInstructionData', + }), + (value) => ({ ...value, discriminator: 3 }) + ) as Serializer; +} + +// Instruction. +export function burn( + context: Pick, + input: BurnInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + collection: { index: 1, isWritable: true, value: input.collection ?? null }, + authority: { index: 2, isWritable: false, value: input.authority ?? null }, + payer: { index: 3, isWritable: true, value: input.payer ?? null }, + logWrapper: { + index: 4, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Default values. + if (!resolvedAccounts.authority.value) { + resolvedAccounts.authority.value = context.identity; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getBurnInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/compress.ts b/clients/js/src/generated/instructions/compress.ts new file mode 100644 index 00000000..f9ee6d99 --- /dev/null +++ b/clients/js/src/generated/instructions/compress.ts @@ -0,0 +1,126 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type CompressInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The owner or delegate of the asset */ + owner: Signer; + /** The account receiving the storage fees */ + payer?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type CompressInstructionData = { discriminator: number }; + +export type CompressInstructionDataArgs = {}; + +export function getCompressInstructionDataSerializer(): Serializer< + CompressInstructionDataArgs, + CompressInstructionData +> { + return mapSerializer< + CompressInstructionDataArgs, + any, + CompressInstructionData + >( + struct([['discriminator', u8()]], { + description: 'CompressInstructionData', + }), + (value) => ({ ...value, discriminator: 8 }) + ) as Serializer; +} + +// Instruction. +export function compress( + context: Pick, + input: CompressInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + owner: { index: 1, isWritable: false, value: input.owner ?? null }, + payer: { index: 2, isWritable: true, value: input.payer ?? null }, + systemProgram: { + index: 3, + isWritable: false, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Default values. + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getCompressInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/create.ts b/clients/js/src/generated/instructions/create.ts index 94cefccd..1c4d4f0c 100644 --- a/clients/js/src/generated/instructions/create.ts +++ b/clients/js/src/generated/instructions/create.ts @@ -16,7 +16,6 @@ import { } from '@metaplex-foundation/umi'; import { Serializer, - bool, mapSerializer, string, struct, @@ -33,8 +32,10 @@ import { DataState, DataStateArgs, getDataStateSerializer } from '../types'; export type CreateInstructionAccounts = { /** The address of the new asset */ assetAddress: Signer; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; /** The authority of the new asset */ - authority?: PublicKey | Pda; + updateAuthority?: PublicKey | Pda; /** The account paying for the storage fees */ payer?: Signer; /** The owner of the new asset. Defaults to the authority if not present. */ @@ -49,14 +50,12 @@ export type CreateInstructionAccounts = { export type CreateInstructionData = { discriminator: number; dataState: DataState; - watermark: boolean; name: string; uri: string; }; export type CreateInstructionDataArgs = { dataState: DataStateArgs; - watermark: boolean; name: string; uri: string; }; @@ -70,7 +69,6 @@ export function getCreateInstructionDataSerializer(): Serializer< [ ['discriminator', u8()], ['dataState', getDataStateSerializer()], - ['watermark', bool()], ['name', string()], ['uri', string()], ], @@ -101,16 +99,21 @@ export function create( isWritable: true, value: input.assetAddress ?? null, }, - authority: { index: 1, isWritable: false, value: input.authority ?? null }, - payer: { index: 2, isWritable: true, value: input.payer ?? null }, - owner: { index: 3, isWritable: false, value: input.owner ?? null }, + collection: { index: 1, isWritable: true, value: input.collection ?? null }, + updateAuthority: { + index: 2, + isWritable: false, + value: input.updateAuthority ?? null, + }, + payer: { index: 3, isWritable: true, value: input.payer ?? null }, + owner: { index: 4, isWritable: false, value: input.owner ?? null }, systemProgram: { - index: 4, + index: 5, isWritable: false, value: input.systemProgram ?? null, }, logWrapper: { - index: 5, + index: 6, isWritable: false, value: input.logWrapper ?? null, }, diff --git a/clients/js/src/generated/instructions/decompress.ts b/clients/js/src/generated/instructions/decompress.ts new file mode 100644 index 00000000..487df5fb --- /dev/null +++ b/clients/js/src/generated/instructions/decompress.ts @@ -0,0 +1,126 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type DecompressInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The owner or delegate of the asset */ + owner: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type DecompressInstructionData = { discriminator: number }; + +export type DecompressInstructionDataArgs = {}; + +export function getDecompressInstructionDataSerializer(): Serializer< + DecompressInstructionDataArgs, + DecompressInstructionData +> { + return mapSerializer< + DecompressInstructionDataArgs, + any, + DecompressInstructionData + >( + struct([['discriminator', u8()]], { + description: 'DecompressInstructionData', + }), + (value) => ({ ...value, discriminator: 9 }) + ) as Serializer; +} + +// Instruction. +export function decompress( + context: Pick, + input: DecompressInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + owner: { index: 1, isWritable: false, value: input.owner ?? null }, + payer: { index: 2, isWritable: true, value: input.payer ?? null }, + systemProgram: { + index: 3, + isWritable: false, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 4, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Default values. + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getDecompressInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/delegate.ts b/clients/js/src/generated/instructions/delegate.ts new file mode 100644 index 00000000..a5bf6d32 --- /dev/null +++ b/clients/js/src/generated/instructions/delegate.ts @@ -0,0 +1,136 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type DelegateInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The owner of the asset */ + owner: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** The new simple delegate for the asset */ + delegate: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type DelegateInstructionData = { discriminator: number }; + +export type DelegateInstructionDataArgs = {}; + +export function getDelegateInstructionDataSerializer(): Serializer< + DelegateInstructionDataArgs, + DelegateInstructionData +> { + return mapSerializer< + DelegateInstructionDataArgs, + any, + DelegateInstructionData + >( + struct([['discriminator', u8()]], { + description: 'DelegateInstructionData', + }), + (value) => ({ ...value, discriminator: 2 }) + ) as Serializer; +} + +// Instruction. +export function delegate( + context: Pick, + input: DelegateInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + collection: { + index: 1, + isWritable: false, + value: input.collection ?? null, + }, + owner: { index: 2, isWritable: true, value: input.owner ?? null }, + payer: { index: 3, isWritable: true, value: input.payer ?? null }, + delegate: { index: 4, isWritable: false, value: input.delegate ?? null }, + systemProgram: { + index: 5, + isWritable: false, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 6, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Default values. + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getDelegateInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/freeze.ts b/clients/js/src/generated/instructions/freeze.ts new file mode 100644 index 00000000..5d4b2d09 --- /dev/null +++ b/clients/js/src/generated/instructions/freeze.ts @@ -0,0 +1,103 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type FreezeInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The delegate of the asset */ + delegate: Signer; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type FreezeInstructionData = { discriminator: number }; + +export type FreezeInstructionDataArgs = {}; + +export function getFreezeInstructionDataSerializer(): Serializer< + FreezeInstructionDataArgs, + FreezeInstructionData +> { + return mapSerializer( + struct([['discriminator', u8()]], { + description: 'FreezeInstructionData', + }), + (value) => ({ ...value, discriminator: 6 }) + ) as Serializer; +} + +// Instruction. +export function freeze( + context: Pick, + input: FreezeInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + delegate: { index: 1, isWritable: false, value: input.delegate ?? null }, + logWrapper: { + index: 2, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getFreezeInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 88b73e1d..763e8bf7 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -6,4 +6,13 @@ * @see https://github.com/metaplex-foundation/kinobi */ +export * from './burn'; +export * from './compress'; export * from './create'; +export * from './decompress'; +export * from './delegate'; +export * from './freeze'; +export * from './migrate'; +export * from './thaw'; +export * from './transfer'; +export * from './update'; diff --git a/clients/js/src/generated/instructions/migrate.ts b/clients/js/src/generated/instructions/migrate.ts new file mode 100644 index 00000000..d3e2d070 --- /dev/null +++ b/clients/js/src/generated/instructions/migrate.ts @@ -0,0 +1,217 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; +import { + DataState, + DataStateArgs, + MigrationLevel, + MigrationLevelArgs, + getDataStateSerializer, + getMigrationLevelSerializer, +} from '../types'; + +// Accounts. +export type MigrateInstructionAccounts = { + /** The address of the new asset */ + assetAddress: Signer; + /** The authority of the new asset */ + owner?: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** mpl-token-metadata collection metadata or mpl-asset collection */ + collection?: PublicKey | Pda; + /** Token account */ + token: PublicKey | Pda; + /** Mint of token asset */ + mint: PublicKey | Pda; + /** Metadata (pda of ['metadata', program id, mint id]) */ + metadata: PublicKey | Pda; + /** Edition of token asset */ + edition?: PublicKey | Pda; + /** Owner token record account */ + ownerTokenRecord?: PublicKey | Pda; + /** SPL Token Program */ + splTokenProgram?: PublicKey | Pda; + /** SPL Associated Token Account program */ + splAtaProgram?: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; + /** Token Authorization Rules Program */ + authorizationRulesProgram?: PublicKey | Pda; + /** Token Authorization Rules account */ + authorizationRules?: PublicKey | Pda; +}; + +// Data. +export type MigrateInstructionData = { + discriminator: number; + dataState: DataState; + level: MigrationLevel; +}; + +export type MigrateInstructionDataArgs = { + dataState: DataStateArgs; + level: MigrationLevelArgs; +}; + +export function getMigrateInstructionDataSerializer(): Serializer< + MigrateInstructionDataArgs, + MigrateInstructionData +> { + return mapSerializer( + struct( + [ + ['discriminator', u8()], + ['dataState', getDataStateSerializer()], + ['level', getMigrationLevelSerializer()], + ], + { description: 'MigrateInstructionData' } + ), + (value) => ({ ...value, discriminator: 1 }) + ) as Serializer; +} + +// Args. +export type MigrateInstructionArgs = MigrateInstructionDataArgs; + +// Instruction. +export function migrate( + context: Pick, + input: MigrateInstructionAccounts & MigrateInstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + owner: { index: 1, isWritable: false, value: input.owner ?? null }, + payer: { index: 2, isWritable: true, value: input.payer ?? null }, + collection: { index: 3, isWritable: true, value: input.collection ?? null }, + token: { index: 4, isWritable: true, value: input.token ?? null }, + mint: { index: 5, isWritable: true, value: input.mint ?? null }, + metadata: { index: 6, isWritable: true, value: input.metadata ?? null }, + edition: { index: 7, isWritable: true, value: input.edition ?? null }, + ownerTokenRecord: { + index: 8, + isWritable: true, + value: input.ownerTokenRecord ?? null, + }, + splTokenProgram: { + index: 9, + isWritable: false, + value: input.splTokenProgram ?? null, + }, + splAtaProgram: { + index: 10, + isWritable: false, + value: input.splAtaProgram ?? null, + }, + systemProgram: { + index: 11, + isWritable: false, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 12, + isWritable: false, + value: input.logWrapper ?? null, + }, + authorizationRulesProgram: { + index: 13, + isWritable: false, + value: input.authorizationRulesProgram ?? null, + }, + authorizationRules: { + index: 14, + isWritable: false, + value: input.authorizationRules ?? null, + }, + }; + + // Arguments. + const resolvedArgs: MigrateInstructionArgs = { ...input }; + + // Default values. + if (!resolvedAccounts.payer.value) { + resolvedAccounts.payer.value = context.payer; + } + if (!resolvedAccounts.splTokenProgram.value) { + resolvedAccounts.splTokenProgram.value = context.programs.getPublicKey( + 'splToken', + 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + ); + resolvedAccounts.splTokenProgram.isWritable = false; + } + if (!resolvedAccounts.splAtaProgram.value) { + resolvedAccounts.splAtaProgram.value = context.programs.getPublicKey( + 'splAssociatedToken', + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' + ); + resolvedAccounts.splAtaProgram.isWritable = false; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getMigrateInstructionDataSerializer().serialize( + resolvedArgs as MigrateInstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/thaw.ts b/clients/js/src/generated/instructions/thaw.ts new file mode 100644 index 00000000..3ceb9ae0 --- /dev/null +++ b/clients/js/src/generated/instructions/thaw.ts @@ -0,0 +1,103 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type ThawInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The delegate of the asset */ + delegate: Signer; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type ThawInstructionData = { discriminator: number }; + +export type ThawInstructionDataArgs = {}; + +export function getThawInstructionDataSerializer(): Serializer< + ThawInstructionDataArgs, + ThawInstructionData +> { + return mapSerializer( + struct([['discriminator', u8()]], { + description: 'ThawInstructionData', + }), + (value) => ({ ...value, discriminator: 7 }) + ) as Serializer; +} + +// Instruction. +export function thaw( + context: Pick, + input: ThawInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + delegate: { index: 1, isWritable: false, value: input.delegate ?? null }, + logWrapper: { + index: 2, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getThawInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/transfer.ts b/clients/js/src/generated/instructions/transfer.ts new file mode 100644 index 00000000..bc60e6e2 --- /dev/null +++ b/clients/js/src/generated/instructions/transfer.ts @@ -0,0 +1,125 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type TransferInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The collection to which the asset belongs */ + collection?: PublicKey | Pda; + /** The owner or delegate of the asset */ + authority?: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** The new owner to which to transfer the asset */ + newOwner: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type TransferInstructionData = { discriminator: number }; + +export type TransferInstructionDataArgs = {}; + +export function getTransferInstructionDataSerializer(): Serializer< + TransferInstructionDataArgs, + TransferInstructionData +> { + return mapSerializer< + TransferInstructionDataArgs, + any, + TransferInstructionData + >( + struct([['discriminator', u8()]], { + description: 'TransferInstructionData', + }), + (value) => ({ ...value, discriminator: 4 }) + ) as Serializer; +} + +// Instruction. +export function transfer( + context: Pick, + input: TransferInstructionAccounts +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + collection: { + index: 1, + isWritable: false, + value: input.collection ?? null, + }, + authority: { index: 2, isWritable: false, value: input.authority ?? null }, + payer: { index: 3, isWritable: true, value: input.payer ?? null }, + newOwner: { index: 4, isWritable: false, value: input.newOwner ?? null }, + logWrapper: { + index: 5, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Default values. + if (!resolvedAccounts.authority.value) { + resolvedAccounts.authority.value = context.identity; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getTransferInstructionDataSerializer().serialize({}); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/instructions/update.ts b/clients/js/src/generated/instructions/update.ts new file mode 100644 index 00000000..f62b36e7 --- /dev/null +++ b/clients/js/src/generated/instructions/update.ts @@ -0,0 +1,150 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Context, + Pda, + PublicKey, + Signer, + TransactionBuilder, + transactionBuilder, +} from '@metaplex-foundation/umi'; +import { + Serializer, + mapSerializer, + string, + struct, + u8, +} from '@metaplex-foundation/umi/serializers'; +import { + ResolvedAccount, + ResolvedAccountsWithIndices, + getAccountMetasAndSigners, +} from '../shared'; + +// Accounts. +export type UpdateInstructionAccounts = { + /** The address of the asset */ + assetAddress: PublicKey | Pda; + /** The update authority or update authority delegate of the asset */ + authority?: Signer; + /** The account paying for the storage fees */ + payer?: Signer; + /** The new update authority of the asset */ + newUpdateAuthority?: PublicKey | Pda; + /** The system program */ + systemProgram?: PublicKey | Pda; + /** The SPL Noop Program */ + logWrapper?: PublicKey | Pda; +}; + +// Data. +export type UpdateInstructionData = { + discriminator: number; + newName: string; + newUri: string; +}; + +export type UpdateInstructionDataArgs = { newName: string; newUri: string }; + +export function getUpdateInstructionDataSerializer(): Serializer< + UpdateInstructionDataArgs, + UpdateInstructionData +> { + return mapSerializer( + struct( + [ + ['discriminator', u8()], + ['newName', string()], + ['newUri', string()], + ], + { description: 'UpdateInstructionData' } + ), + (value) => ({ ...value, discriminator: 5 }) + ) as Serializer; +} + +// Args. +export type UpdateInstructionArgs = UpdateInstructionDataArgs; + +// Instruction. +export function update( + context: Pick, + input: UpdateInstructionAccounts & UpdateInstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + + // Accounts. + const resolvedAccounts: ResolvedAccountsWithIndices = { + assetAddress: { + index: 0, + isWritable: true, + value: input.assetAddress ?? null, + }, + authority: { index: 1, isWritable: false, value: input.authority ?? null }, + payer: { index: 2, isWritable: true, value: input.payer ?? null }, + newUpdateAuthority: { + index: 3, + isWritable: false, + value: input.newUpdateAuthority ?? null, + }, + systemProgram: { + index: 4, + isWritable: false, + value: input.systemProgram ?? null, + }, + logWrapper: { + index: 5, + isWritable: false, + value: input.logWrapper ?? null, + }, + }; + + // Arguments. + const resolvedArgs: UpdateInstructionArgs = { ...input }; + + // Default values. + if (!resolvedAccounts.authority.value) { + resolvedAccounts.authority.value = context.identity; + } + if (!resolvedAccounts.systemProgram.value) { + resolvedAccounts.systemProgram.value = context.programs.getPublicKey( + 'splSystem', + '11111111111111111111111111111111' + ); + resolvedAccounts.systemProgram.isWritable = false; + } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values( + resolvedAccounts + ).sort((a, b) => a.index - b.index); + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners( + orderedAccounts, + 'programId', + programId + ); + + // Data. + const data = getUpdateInstructionDataSerializer().serialize( + resolvedArgs as UpdateInstructionDataArgs + ); + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0; + + return transactionBuilder([ + { instruction: { keys, programId, data }, signers, bytesCreatedOnChain }, + ]); +} diff --git a/clients/js/src/generated/types/authority.ts b/clients/js/src/generated/types/authority.ts index 249ab4f2..427fbdc3 100644 --- a/clients/js/src/generated/types/authority.ts +++ b/clients/js/src/generated/types/authority.ts @@ -21,12 +21,14 @@ import { Plugin, PluginArgs, getPluginSerializer } from '.'; export type Authority = | { __kind: 'Owner' } | { __kind: 'Permanent'; address: PublicKey } - | { __kind: 'SameAs'; plugin: Plugin }; + | { __kind: 'SameAs'; plugin: Plugin } + | { __kind: 'Collection' }; export type AuthorityArgs = | { __kind: 'Owner' } | { __kind: 'Permanent'; address: PublicKey } - | { __kind: 'SameAs'; plugin: PluginArgs }; + | { __kind: 'SameAs'; plugin: PluginArgs } + | { __kind: 'Collection' }; export function getAuthoritySerializer(): Serializer { return dataEnum( @@ -44,6 +46,7 @@ export function getAuthoritySerializer(): Serializer { ['plugin', getPluginSerializer()], ]), ], + ['Collection', unit()], ], { description: 'Authority' } ) as Serializer; @@ -61,6 +64,9 @@ export function authority( kind: 'SameAs', data: GetDataEnumKindContent ): GetDataEnumKind; +export function authority( + kind: 'Collection' +): GetDataEnumKind; export function authority( kind: K, data?: any diff --git a/clients/js/src/generated/types/collection.ts b/clients/js/src/generated/types/collection.ts new file mode 100644 index 00000000..977441ab --- /dev/null +++ b/clients/js/src/generated/types/collection.ts @@ -0,0 +1,32 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + bool, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; + +export type Collection = { collectionAddress: PublicKey; required: boolean }; + +export type CollectionArgs = Collection; + +export function getCollectionSerializer(): Serializer< + CollectionArgs, + Collection +> { + return struct( + [ + ['collectionAddress', publicKeySerializer()], + ['required', bool()], + ], + { description: 'Collection' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/compressionProof.ts b/clients/js/src/generated/types/compressionProof.ts new file mode 100644 index 00000000..a22a1f1c --- /dev/null +++ b/clients/js/src/generated/types/compressionProof.ts @@ -0,0 +1,22 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; + +export type CompressionProof = {}; + +export type CompressionProofArgs = CompressionProof; + +export function getCompressionProofSerializer(): Serializer< + CompressionProofArgs, + CompressionProof +> { + return struct([], { + description: 'CompressionProof', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/extraAccounts.ts b/clients/js/src/generated/types/extraAccounts.ts new file mode 100644 index 00000000..4e2958ae --- /dev/null +++ b/clients/js/src/generated/types/extraAccounts.ts @@ -0,0 +1,92 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Option, OptionOrNullable, PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + option, + publicKey as publicKeySerializer, + struct, + unit, +} from '@metaplex-foundation/umi/serializers'; + +export type ExtraAccounts = + | { __kind: 'None' } + | { __kind: 'SplHook'; extraAccountMetas: PublicKey } + | { + __kind: 'MplHook'; + mintPda: Option; + collectionPda: Option; + ownerPda: Option; + }; + +export type ExtraAccountsArgs = + | { __kind: 'None' } + | { __kind: 'SplHook'; extraAccountMetas: PublicKey } + | { + __kind: 'MplHook'; + mintPda: OptionOrNullable; + collectionPda: OptionOrNullable; + ownerPda: OptionOrNullable; + }; + +export function getExtraAccountsSerializer(): Serializer< + ExtraAccountsArgs, + ExtraAccounts +> { + return dataEnum( + [ + ['None', unit()], + [ + 'SplHook', + struct>([ + ['extraAccountMetas', publicKeySerializer()], + ]), + ], + [ + 'MplHook', + struct>([ + ['mintPda', option(publicKeySerializer())], + ['collectionPda', option(publicKeySerializer())], + ['ownerPda', option(publicKeySerializer())], + ]), + ], + ], + { description: 'ExtraAccounts' } + ) as Serializer; +} + +// Data Enum Helpers. +export function extraAccounts( + kind: 'None' +): GetDataEnumKind; +export function extraAccounts( + kind: 'SplHook', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function extraAccounts( + kind: 'MplHook', + data: GetDataEnumKindContent +): GetDataEnumKind; +export function extraAccounts( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isExtraAccounts( + kind: K, + value: ExtraAccounts +): value is ExtraAccounts & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 64c3661d..95aff73f 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -6,10 +6,16 @@ * @see https://github.com/metaplex-foundation/kinobi */ -export * from './assetHeader'; export * from './authority'; +export * from './collection'; +export * from './compressionProof'; export * from './creator'; export * from './dataState'; +export * from './extraAccounts'; export * from './key'; +export * from './migrationLevel'; export * from './plugin'; +export * from './pluginHeader'; +export * from './pluginRegistry'; +export * from './registryData'; export * from './royalties'; diff --git a/clients/js/src/generated/types/migrationLevel.ts b/clients/js/src/generated/types/migrationLevel.ts new file mode 100644 index 00000000..b6d3affe --- /dev/null +++ b/clients/js/src/generated/types/migrationLevel.ts @@ -0,0 +1,25 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} + +export type MigrationLevelArgs = MigrationLevel; + +export function getMigrationLevelSerializer(): Serializer< + MigrationLevelArgs, + MigrationLevel +> { + return scalarEnum(MigrationLevel, { + description: 'MigrationLevel', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index 0c84968b..a0130bcb 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -10,8 +10,6 @@ import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; export enum Plugin { Reserved, - Asset, - HashedAsset, Royalties, MasterEdition, PrintEdition, diff --git a/clients/js/src/generated/types/assetHeader.ts b/clients/js/src/generated/types/pluginHeader.ts similarity index 59% rename from clients/js/src/generated/types/assetHeader.ts rename to clients/js/src/generated/types/pluginHeader.ts index c8b2f945..7bd9bf77 100644 --- a/clients/js/src/generated/types/assetHeader.ts +++ b/clients/js/src/generated/types/pluginHeader.ts @@ -13,22 +13,22 @@ import { u8, } from '@metaplex-foundation/umi/serializers'; -export type AssetHeader = { version: number; pluginMapOffset: bigint }; +export type PluginHeader = { version: number; pluginMapOffset: bigint }; -export type AssetHeaderArgs = { +export type PluginHeaderArgs = { version: number; pluginMapOffset: number | bigint; }; -export function getAssetHeaderSerializer(): Serializer< - AssetHeaderArgs, - AssetHeader +export function getPluginHeaderSerializer(): Serializer< + PluginHeaderArgs, + PluginHeader > { - return struct( + return struct( [ ['version', u8()], ['pluginMapOffset', u64()], ], - { description: 'AssetHeader' } - ) as Serializer; + { description: 'PluginHeader' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/pluginRegistry.ts b/clients/js/src/generated/types/pluginRegistry.ts new file mode 100644 index 00000000..d59e7739 --- /dev/null +++ b/clients/js/src/generated/types/pluginRegistry.ts @@ -0,0 +1,43 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Serializer, + array, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; +import { + Key, + KeyArgs, + RegistryData, + RegistryDataArgs, + getKeySerializer, + getRegistryDataSerializer, +} from '.'; + +export type PluginRegistry = { registry: Array<[Key, RegistryData]> }; + +export type PluginRegistryArgs = { + registry: Array<[KeyArgs, RegistryDataArgs]>; +}; + +export function getPluginRegistrySerializer(): Serializer< + PluginRegistryArgs, + PluginRegistry +> { + return struct( + [ + [ + 'registry', + array(tuple([getKeySerializer(), getRegistryDataSerializer()])), + ], + ], + { description: 'PluginRegistry' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/registryData.ts b/clients/js/src/generated/types/registryData.ts new file mode 100644 index 00000000..055b92cb --- /dev/null +++ b/clients/js/src/generated/types/registryData.ts @@ -0,0 +1,35 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Serializer, + array, + struct, + u64, +} from '@metaplex-foundation/umi/serializers'; +import { Authority, AuthorityArgs, getAuthoritySerializer } from '.'; + +export type RegistryData = { offset: bigint; authorities: Array }; + +export type RegistryDataArgs = { + offset: number | bigint; + authorities: Array; +}; + +export function getRegistryDataSerializer(): Serializer< + RegistryDataArgs, + RegistryData +> { + return struct( + [ + ['offset', u64()], + ['authorities', array(getAuthoritySerializer())], + ], + { description: 'RegistryData' } + ) as Serializer; +} diff --git a/clients/js/test/create.test.ts b/clients/js/test/create.test.ts index ae159e4b..89a9b1ba 100644 --- a/clients/js/test/create.test.ts +++ b/clients/js/test/create.test.ts @@ -12,7 +12,6 @@ test('it can create a new asset in account state', async (t) => { // When we create a new account. await create(umi, { dataState: DataState.AccountState, - watermark: false, assetAddress, name: 'Test Bread', uri: 'https://example.com/bread', @@ -38,7 +37,6 @@ test('it can create a new asset in ledger state', async (t) => { // When we create a new account. const txResult = await create(umi, { dataState: DataState.LedgerState, - watermark: false, assetAddress, name: 'Test Bread', uri: 'https://example.com/bread', diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/delegate.test.ts new file mode 100644 index 00000000..82d4af53 --- /dev/null +++ b/clients/js/test/delegate.test.ts @@ -0,0 +1,43 @@ +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +// import { base58 } from '@metaplex-foundation/umi/serializers'; +import { DataState, create, delegate, fetchAsset, getAssetAccountDataSerializer, getPluginHeaderSerializer, getPluginRegistrySerializer, } from '../src'; +import { createUmi } from './_setup'; + +test('it initializes the plugin system correctly', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const delegateAddress = 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); + + // Then an account was created with the correct data. + const asset = await fetchAsset(umi, assetAddress.publicKey); + console.log("Before Plugins:\n", asset); + const assetData = getAssetAccountDataSerializer().serialize(asset); + + await delegate(umi, { + assetAddress: assetAddress.publicKey, + owner: umi.identity, + delegate: delegateAddress.publicKey, + }).sendAndConfirm(umi); + + const pluginData = await umi.rpc.getAccount(assetAddress.publicKey); + if (pluginData.exists) { + const pluginHeader = getPluginHeaderSerializer().deserialize(pluginData.data, assetData.length); + console.log("After Plugins:\n", pluginHeader); + const pluginRegistry = getPluginRegistrySerializer().deserialize(pluginData.data, Number(pluginHeader[0].pluginMapOffset)); + console.log(pluginRegistry); + } else { + t.fail("Plugin data not found"); + } + + t.pass(); +}); diff --git a/clients/js/test/info.test.ts b/clients/js/test/info.test.ts index bfd39352..09511cd7 100644 --- a/clients/js/test/info.test.ts +++ b/clients/js/test/info.test.ts @@ -11,7 +11,6 @@ test('fetch account info for account state', async (t) => { // When we create a new account. await create(umi, { dataState: DataState.AccountState, - watermark: false, assetAddress, name: 'Test Bread', uri: 'https://example.com/bread', @@ -38,7 +37,6 @@ test('fetch account info for ledger state', async (t) => { // When we create a new account. await create(umi, { dataState: DataState.LedgerState, - watermark: true, assetAddress, name: 'Test Bread', uri: 'https://example.com/bread', diff --git a/clients/rust/src/generated/accounts/hashed_asset.rs b/clients/rust/src/generated/accounts/hashed_asset.rs index 2c609193..84e11a42 100644 --- a/clients/rust/src/generated/accounts/hashed_asset.rs +++ b/clients/rust/src/generated/accounts/hashed_asset.rs @@ -14,10 +14,11 @@ use borsh::BorshSerialize; pub struct HashedAsset { pub key: Key, pub hash: [u8; 32], - pub watermark_slot: Option, } impl HashedAsset { + pub const LEN: usize = 33; + #[inline(always)] pub fn from_bytes(data: &[u8]) -> Result { let mut data = data; diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index f42a36d6..e80bbb5d 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -19,6 +19,12 @@ pub enum MplAssetError { /// 2 (0x2) - Error serializing account #[error("Error serializing account")] SerializationError, + /// 3 (0x3) - Plugins not initialized + #[error("Plugins not initialized")] + PluginsNotInitialized, + /// 4 (0x4) - Plugin not found + #[error("Plugin not found")] + PluginNotFound, } impl solana_program::program_error::PrintProgramError for MplAssetError { diff --git a/clients/rust/src/generated/instructions/burn.rs b/clients/rust/src/generated/instructions/burn.rs new file mode 100644 index 00000000..a8e84d9e --- /dev/null +++ b/clients/rust/src/generated/instructions/burn.rs @@ -0,0 +1,477 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Burn { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The owner or delegate of the asset + pub authority: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: Option, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Burn { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.authority, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = BurnInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct BurnInstructionData { + discriminator: u8, +} + +impl BurnInstructionData { + fn new() -> Self { + Self { discriminator: 3 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct BurnBuilder { + asset_address: Option, + collection: Option, + authority: Option, + payer: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl BurnBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { + self.authority = Some(authority); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: Option) -> &mut Self { + self.payer = payer; + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Burn { + asset_address: self.asset_address.expect("asset_address is not set"), + collection: self.collection, + authority: self.authority.expect("authority is not set"), + payer: self.payer, + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `burn` CPI accounts. +pub struct BurnCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The owner or delegate of the asset + pub authority: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `burn` CPI instruction. +pub struct BurnCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The owner or delegate of the asset + pub authority: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> BurnCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: BurnCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + collection: accounts.collection, + authority: accounts.authority, + payer: accounts.payer, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.authority.key, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new( + *payer.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = BurnInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.authority.clone()); + if let Some(payer) = self.payer { + account_infos.push(payer.clone()); + } + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `burn` CPI instruction builder. +pub struct BurnCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> BurnCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(BurnCpiBuilderInstruction { + __program: program, + asset_address: None, + collection: None, + authority: None, + payer: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.authority = Some(authority); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer( + &mut self, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.payer = payer; + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = BurnCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + collection: self.instruction.collection, + + authority: self.instruction.authority.expect("authority is not set"), + + payer: self.instruction.payer, + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct BurnCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/compress.rs b/clients/rust/src/generated/instructions/compress.rs new file mode 100644 index 00000000..8471c0e9 --- /dev/null +++ b/clients/rust/src/generated/instructions/compress.rs @@ -0,0 +1,462 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Compress { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub owner: solana_program::pubkey::Pubkey, + /// The account receiving the storage fees + pub payer: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Compress { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.owner, true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = CompressInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct CompressInstructionData { + discriminator: u8, +} + +impl CompressInstructionData { + fn new() -> Self { + Self { discriminator: 8 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct CompressBuilder { + asset_address: Option, + owner: Option, + payer: Option, + system_program: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl CompressBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn owner(&mut self, owner: solana_program::pubkey::Pubkey) -> &mut Self { + self.owner = Some(owner); + self + } + /// `[optional account]` + /// The account receiving the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: Option) -> &mut Self { + self.payer = payer; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Compress { + asset_address: self.asset_address.expect("asset_address is not set"), + owner: self.owner.expect("owner is not set"), + payer: self.payer, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `compress` CPI accounts. +pub struct CompressCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The account receiving the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `compress` CPI instruction. +pub struct CompressCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The account receiving the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> CompressCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: CompressCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + owner: accounts.owner, + payer: accounts.payer, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.owner.key, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new( + *payer.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = CompressInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + account_infos.push(self.owner.clone()); + if let Some(payer) = self.payer { + account_infos.push(payer.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `compress` CPI instruction builder. +pub struct CompressCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> CompressCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(CompressCpiBuilderInstruction { + __program: program, + asset_address: None, + owner: None, + payer: None, + system_program: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn owner(&mut self, owner: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.owner = Some(owner); + self + } + /// `[optional account]` + /// The account receiving the storage fees + #[inline(always)] + pub fn payer( + &mut self, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.payer = payer; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = CompressCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + owner: self.instruction.owner.expect("owner is not set"), + + payer: self.instruction.payer, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct CompressCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/create.rs b/clients/rust/src/generated/instructions/create.rs index a8b7419c..5406a7a1 100644 --- a/clients/rust/src/generated/instructions/create.rs +++ b/clients/rust/src/generated/instructions/create.rs @@ -13,8 +13,10 @@ use borsh::BorshSerialize; pub struct Create { /// The address of the new asset pub asset_address: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, /// The authority of the new asset - pub authority: Option, + pub update_authority: Option, /// The account paying for the storage fees pub payer: solana_program::pubkey::Pubkey, /// The owner of the new asset. Defaults to the authority if not present. @@ -38,14 +40,25 @@ impl Create { args: CreateInstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { - let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( self.asset_address, true, )); - if let Some(authority) = self.authority { + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(update_authority) = self.update_authority { accounts.push(solana_program::instruction::AccountMeta::new_readonly( - authority, false, + update_authority, + false, )); } else { accounts.push(solana_program::instruction::AccountMeta::new_readonly( @@ -109,7 +122,6 @@ impl CreateInstructionData { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct CreateInstructionArgs { pub data_state: DataState, - pub watermark: bool, pub name: String, pub uri: String, } @@ -118,13 +130,13 @@ pub struct CreateInstructionArgs { #[derive(Default)] pub struct CreateBuilder { asset_address: Option, - authority: Option, + collection: Option, + update_authority: Option, payer: Option, owner: Option, system_program: Option, log_wrapper: Option, data_state: Option, - watermark: Option, name: Option, uri: Option, __remaining_accounts: Vec, @@ -141,10 +153,20 @@ impl CreateBuilder { self } /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// `[optional account]` /// The authority of the new asset #[inline(always)] - pub fn authority(&mut self, authority: Option) -> &mut Self { - self.authority = authority; + pub fn update_authority( + &mut self, + update_authority: Option, + ) -> &mut Self { + self.update_authority = update_authority; self } /// The account paying for the storage fees @@ -183,11 +205,6 @@ impl CreateBuilder { self } #[inline(always)] - pub fn watermark(&mut self, watermark: bool) -> &mut Self { - self.watermark = Some(watermark); - self - } - #[inline(always)] pub fn name(&mut self, name: String) -> &mut Self { self.name = Some(name); self @@ -219,7 +236,8 @@ impl CreateBuilder { pub fn instruction(&self) -> solana_program::instruction::Instruction { let accounts = Create { asset_address: self.asset_address.expect("asset_address is not set"), - authority: self.authority, + collection: self.collection, + update_authority: self.update_authority, payer: self.payer.expect("payer is not set"), owner: self.owner, system_program: self @@ -229,7 +247,6 @@ impl CreateBuilder { }; let args = CreateInstructionArgs { data_state: self.data_state.clone().expect("data_state is not set"), - watermark: self.watermark.clone().expect("watermark is not set"), name: self.name.clone().expect("name is not set"), uri: self.uri.clone().expect("uri is not set"), }; @@ -242,8 +259,10 @@ impl CreateBuilder { pub struct CreateCpiAccounts<'a, 'b> { /// The address of the new asset pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The authority of the new asset - pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The account paying for the storage fees pub payer: &'b solana_program::account_info::AccountInfo<'a>, /// The owner of the new asset. Defaults to the authority if not present. @@ -260,8 +279,10 @@ pub struct CreateCpi<'a, 'b> { pub __program: &'b solana_program::account_info::AccountInfo<'a>, /// The address of the new asset pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The authority of the new asset - pub authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + pub update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// The account paying for the storage fees pub payer: &'b solana_program::account_info::AccountInfo<'a>, /// The owner of the new asset. Defaults to the authority if not present. @@ -283,7 +304,8 @@ impl<'a, 'b> CreateCpi<'a, 'b> { Self { __program: program, asset_address: accounts.asset_address, - authority: accounts.authority, + collection: accounts.collection, + update_authority: accounts.update_authority, payer: accounts.payer, owner: accounts.owner, system_program: accounts.system_program, @@ -324,14 +346,25 @@ impl<'a, 'b> CreateCpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new( *self.asset_address.key, true, )); - if let Some(authority) = self.authority { + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(update_authority) = self.update_authority { accounts.push(solana_program::instruction::AccountMeta::new_readonly( - *authority.key, + *update_authority.key, false, )); } else { @@ -385,11 +418,14 @@ impl<'a, 'b> CreateCpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(7 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.asset_address.clone()); - if let Some(authority) = self.authority { - account_infos.push(authority.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + if let Some(update_authority) = self.update_authority { + account_infos.push(update_authority.clone()); } account_infos.push(self.payer.clone()); if let Some(owner) = self.owner { @@ -421,13 +457,13 @@ impl<'a, 'b> CreateCpiBuilder<'a, 'b> { let instruction = Box::new(CreateCpiBuilderInstruction { __program: program, asset_address: None, - authority: None, + collection: None, + update_authority: None, payer: None, owner: None, system_program: None, log_wrapper: None, data_state: None, - watermark: None, name: None, uri: None, __remaining_accounts: Vec::new(), @@ -444,13 +480,23 @@ impl<'a, 'b> CreateCpiBuilder<'a, 'b> { self } /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// `[optional account]` /// The authority of the new asset #[inline(always)] - pub fn authority( + pub fn update_authority( &mut self, - authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, ) -> &mut Self { - self.instruction.authority = authority; + self.instruction.update_authority = update_authority; self } /// The account paying for the storage fees @@ -494,11 +540,6 @@ impl<'a, 'b> CreateCpiBuilder<'a, 'b> { self } #[inline(always)] - pub fn watermark(&mut self, watermark: bool) -> &mut Self { - self.instruction.watermark = Some(watermark); - self - } - #[inline(always)] pub fn name(&mut self, name: String) -> &mut Self { self.instruction.name = Some(name); self @@ -555,11 +596,6 @@ impl<'a, 'b> CreateCpiBuilder<'a, 'b> { .data_state .clone() .expect("data_state is not set"), - watermark: self - .instruction - .watermark - .clone() - .expect("watermark is not set"), name: self.instruction.name.clone().expect("name is not set"), uri: self.instruction.uri.clone().expect("uri is not set"), }; @@ -571,7 +607,9 @@ impl<'a, 'b> CreateCpiBuilder<'a, 'b> { .asset_address .expect("asset_address is not set"), - authority: self.instruction.authority, + collection: self.instruction.collection, + + update_authority: self.instruction.update_authority, payer: self.instruction.payer.expect("payer is not set"), @@ -595,13 +633,13 @@ impl<'a, 'b> CreateCpiBuilder<'a, 'b> { struct CreateCpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, - authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, data_state: Option, - watermark: Option, name: Option, uri: Option, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. diff --git a/clients/rust/src/generated/instructions/decompress.rs b/clients/rust/src/generated/instructions/decompress.rs new file mode 100644 index 00000000..be299bf5 --- /dev/null +++ b/clients/rust/src/generated/instructions/decompress.rs @@ -0,0 +1,462 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Decompress { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The owner or delegate of the asset + pub owner: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Decompress { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.owner, true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = DecompressInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct DecompressInstructionData { + discriminator: u8, +} + +impl DecompressInstructionData { + fn new() -> Self { + Self { discriminator: 9 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct DecompressBuilder { + asset_address: Option, + owner: Option, + payer: Option, + system_program: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl DecompressBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn owner(&mut self, owner: solana_program::pubkey::Pubkey) -> &mut Self { + self.owner = Some(owner); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: Option) -> &mut Self { + self.payer = payer; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Decompress { + asset_address: self.asset_address.expect("asset_address is not set"), + owner: self.owner.expect("owner is not set"), + payer: self.payer, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `decompress` CPI accounts. +pub struct DecompressCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `decompress` CPI instruction. +pub struct DecompressCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The owner or delegate of the asset + pub owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> DecompressCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: DecompressCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + owner: accounts.owner, + payer: accounts.payer, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.owner.key, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new( + *payer.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = DecompressInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + account_infos.push(self.owner.clone()); + if let Some(payer) = self.payer { + account_infos.push(payer.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `decompress` CPI instruction builder. +pub struct DecompressCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> DecompressCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(DecompressCpiBuilderInstruction { + __program: program, + asset_address: None, + owner: None, + payer: None, + system_program: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn owner(&mut self, owner: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.owner = Some(owner); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer( + &mut self, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.payer = payer; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = DecompressCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + owner: self.instruction.owner.expect("owner is not set"), + + payer: self.instruction.payer, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct DecompressCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/delegate.rs b/clients/rust/src/generated/instructions/delegate.rs new file mode 100644 index 00000000..22333619 --- /dev/null +++ b/clients/rust/src/generated/instructions/delegate.rs @@ -0,0 +1,553 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Delegate { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The owner of the asset + pub owner: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: Option, + /// The new simple delegate for the asset + pub delegate: solana_program::pubkey::Pubkey, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Delegate { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.owner, true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.delegate, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = DelegateInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct DelegateInstructionData { + discriminator: u8, +} + +impl DelegateInstructionData { + fn new() -> Self { + Self { discriminator: 2 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct DelegateBuilder { + asset_address: Option, + collection: Option, + owner: Option, + payer: Option, + delegate: Option, + system_program: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl DelegateBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The owner of the asset + #[inline(always)] + pub fn owner(&mut self, owner: solana_program::pubkey::Pubkey) -> &mut Self { + self.owner = Some(owner); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: Option) -> &mut Self { + self.payer = payer; + self + } + /// The new simple delegate for the asset + #[inline(always)] + pub fn delegate(&mut self, delegate: solana_program::pubkey::Pubkey) -> &mut Self { + self.delegate = Some(delegate); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Delegate { + asset_address: self.asset_address.expect("asset_address is not set"), + collection: self.collection, + owner: self.owner.expect("owner is not set"), + payer: self.payer, + delegate: self.delegate.expect("delegate is not set"), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `delegate` CPI accounts. +pub struct DelegateCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The owner of the asset + pub owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new simple delegate for the asset + pub delegate: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `delegate` CPI instruction. +pub struct DelegateCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The owner of the asset + pub owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new simple delegate for the asset + pub delegate: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> DelegateCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: DelegateCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + collection: accounts.collection, + owner: accounts.owner, + payer: accounts.payer, + delegate: accounts.delegate, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.owner.key, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new( + *payer.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.delegate.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = DelegateInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(7 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.owner.clone()); + if let Some(payer) = self.payer { + account_infos.push(payer.clone()); + } + account_infos.push(self.delegate.clone()); + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `delegate` CPI instruction builder. +pub struct DelegateCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> DelegateCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(DelegateCpiBuilderInstruction { + __program: program, + asset_address: None, + collection: None, + owner: None, + payer: None, + delegate: None, + system_program: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The owner of the asset + #[inline(always)] + pub fn owner(&mut self, owner: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.owner = Some(owner); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer( + &mut self, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.payer = payer; + self + } + /// The new simple delegate for the asset + #[inline(always)] + pub fn delegate( + &mut self, + delegate: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.delegate = Some(delegate); + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = DelegateCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + collection: self.instruction.collection, + + owner: self.instruction.owner.expect("owner is not set"), + + payer: self.instruction.payer, + + delegate: self.instruction.delegate.expect("delegate is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct DelegateCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/freeze.rs b/clients/rust/src/generated/instructions/freeze.rs new file mode 100644 index 00000000..64db0106 --- /dev/null +++ b/clients/rust/src/generated/instructions/freeze.rs @@ -0,0 +1,372 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Freeze { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The delegate of the asset + pub delegate: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Freeze { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.delegate, + true, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = FreezeInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct FreezeInstructionData { + discriminator: u8, +} + +impl FreezeInstructionData { + fn new() -> Self { + Self { discriminator: 6 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct FreezeBuilder { + asset_address: Option, + delegate: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl FreezeBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// The delegate of the asset + #[inline(always)] + pub fn delegate(&mut self, delegate: solana_program::pubkey::Pubkey) -> &mut Self { + self.delegate = Some(delegate); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Freeze { + asset_address: self.asset_address.expect("asset_address is not set"), + delegate: self.delegate.expect("delegate is not set"), + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `freeze` CPI accounts. +pub struct FreezeCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The delegate of the asset + pub delegate: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `freeze` CPI instruction. +pub struct FreezeCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The delegate of the asset + pub delegate: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> FreezeCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: FreezeCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + delegate: accounts.delegate, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.delegate.key, + true, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = FreezeInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + account_infos.push(self.delegate.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `freeze` CPI instruction builder. +pub struct FreezeCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> FreezeCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(FreezeCpiBuilderInstruction { + __program: program, + asset_address: None, + delegate: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// The delegate of the asset + #[inline(always)] + pub fn delegate( + &mut self, + delegate: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.delegate = Some(delegate); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = FreezeCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + delegate: self.instruction.delegate.expect("delegate is not set"), + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct FreezeCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/migrate.rs b/clients/rust/src/generated/instructions/migrate.rs new file mode 100644 index 00000000..ac3d3b84 --- /dev/null +++ b/clients/rust/src/generated/instructions/migrate.rs @@ -0,0 +1,1001 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::DataState; +use crate::generated::types::MigrationLevel; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Migrate { + /// The address of the new asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The authority of the new asset + pub owner: Option, + /// The account paying for the storage fees + pub payer: solana_program::pubkey::Pubkey, + /// mpl-token-metadata collection metadata or mpl-asset collection + pub collection: Option, + /// Token account + pub token: solana_program::pubkey::Pubkey, + /// Mint of token asset + pub mint: solana_program::pubkey::Pubkey, + /// Metadata (pda of ['metadata', program id, mint id]) + pub metadata: solana_program::pubkey::Pubkey, + /// Edition of token asset + pub edition: Option, + /// Owner token record account + pub owner_token_record: Option, + /// SPL Token Program + pub spl_token_program: solana_program::pubkey::Pubkey, + /// SPL Associated Token Account program + pub spl_ata_program: solana_program::pubkey::Pubkey, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, + /// Token Authorization Rules Program + pub authorization_rules_program: Option, + /// Token Authorization Rules account + pub authorization_rules: Option, +} + +impl Migrate { + pub fn instruction( + &self, + args: MigrateInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: MigrateInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(15 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + true, + )); + if let Some(owner) = self.owner { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + owner, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + self.token, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.mint, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.metadata, + false, + )); + if let Some(edition) = self.edition { + accounts.push(solana_program::instruction::AccountMeta::new( + edition, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(owner_token_record) = self.owner_token_record { + accounts.push(solana_program::instruction::AccountMeta::new( + owner_token_record, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.spl_token_program, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.spl_ata_program, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(authorization_rules_program) = self.authorization_rules_program { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authorization_rules_program, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(authorization_rules) = self.authorization_rules { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + authorization_rules, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = MigrateInstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct MigrateInstructionData { + discriminator: u8, +} + +impl MigrateInstructionData { + fn new() -> Self { + Self { discriminator: 1 } + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MigrateInstructionArgs { + pub data_state: DataState, + pub level: MigrationLevel, +} + +/// Instruction builder. +#[derive(Default)] +pub struct MigrateBuilder { + asset_address: Option, + owner: Option, + payer: Option, + collection: Option, + token: Option, + mint: Option, + metadata: Option, + edition: Option, + owner_token_record: Option, + spl_token_program: Option, + spl_ata_program: Option, + system_program: Option, + log_wrapper: Option, + authorization_rules_program: Option, + authorization_rules: Option, + data_state: Option, + level: Option, + __remaining_accounts: Vec, +} + +impl MigrateBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the new asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The authority of the new asset + #[inline(always)] + pub fn owner(&mut self, owner: Option) -> &mut Self { + self.owner = owner; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + self + } + /// `[optional account]` + /// mpl-token-metadata collection metadata or mpl-asset collection + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// Token account + #[inline(always)] + pub fn token(&mut self, token: solana_program::pubkey::Pubkey) -> &mut Self { + self.token = Some(token); + self + } + /// Mint of token asset + #[inline(always)] + pub fn mint(&mut self, mint: solana_program::pubkey::Pubkey) -> &mut Self { + self.mint = Some(mint); + self + } + /// Metadata (pda of ['metadata', program id, mint id]) + #[inline(always)] + pub fn metadata(&mut self, metadata: solana_program::pubkey::Pubkey) -> &mut Self { + self.metadata = Some(metadata); + self + } + /// `[optional account]` + /// Edition of token asset + #[inline(always)] + pub fn edition(&mut self, edition: Option) -> &mut Self { + self.edition = edition; + self + } + /// `[optional account]` + /// Owner token record account + #[inline(always)] + pub fn owner_token_record( + &mut self, + owner_token_record: Option, + ) -> &mut Self { + self.owner_token_record = owner_token_record; + self + } + /// `[optional account, default to 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA']` + /// SPL Token Program + #[inline(always)] + pub fn spl_token_program( + &mut self, + spl_token_program: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.spl_token_program = Some(spl_token_program); + self + } + /// `[optional account, default to 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL']` + /// SPL Associated Token Account program + #[inline(always)] + pub fn spl_ata_program( + &mut self, + spl_ata_program: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.spl_ata_program = Some(spl_ata_program); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// `[optional account]` + /// Token Authorization Rules Program + #[inline(always)] + pub fn authorization_rules_program( + &mut self, + authorization_rules_program: Option, + ) -> &mut Self { + self.authorization_rules_program = authorization_rules_program; + self + } + /// `[optional account]` + /// Token Authorization Rules account + #[inline(always)] + pub fn authorization_rules( + &mut self, + authorization_rules: Option, + ) -> &mut Self { + self.authorization_rules = authorization_rules; + self + } + #[inline(always)] + pub fn data_state(&mut self, data_state: DataState) -> &mut Self { + self.data_state = Some(data_state); + self + } + #[inline(always)] + pub fn level(&mut self, level: MigrationLevel) -> &mut Self { + self.level = Some(level); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Migrate { + asset_address: self.asset_address.expect("asset_address is not set"), + owner: self.owner, + payer: self.payer.expect("payer is not set"), + collection: self.collection, + token: self.token.expect("token is not set"), + mint: self.mint.expect("mint is not set"), + metadata: self.metadata.expect("metadata is not set"), + edition: self.edition, + owner_token_record: self.owner_token_record, + spl_token_program: self.spl_token_program.unwrap_or(solana_program::pubkey!( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + )), + spl_ata_program: self.spl_ata_program.unwrap_or(solana_program::pubkey!( + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + )), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + authorization_rules_program: self.authorization_rules_program, + authorization_rules: self.authorization_rules, + }; + let args = MigrateInstructionArgs { + data_state: self.data_state.clone().expect("data_state is not set"), + level: self.level.clone().expect("level is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `migrate` CPI accounts. +pub struct MigrateCpiAccounts<'a, 'b> { + /// The address of the new asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The authority of the new asset + pub owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// mpl-token-metadata collection metadata or mpl-asset collection + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Token account + pub token: &'b solana_program::account_info::AccountInfo<'a>, + /// Mint of token asset + pub mint: &'b solana_program::account_info::AccountInfo<'a>, + /// Metadata (pda of ['metadata', program id, mint id]) + pub metadata: &'b solana_program::account_info::AccountInfo<'a>, + /// Edition of token asset + pub edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Owner token record account + pub owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// SPL Token Program + pub spl_token_program: &'b solana_program::account_info::AccountInfo<'a>, + /// SPL Associated Token Account program + pub spl_ata_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Token Authorization Rules Program + pub authorization_rules_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Token Authorization Rules account + pub authorization_rules: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `migrate` CPI instruction. +pub struct MigrateCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the new asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The authority of the new asset + pub owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The account paying for the storage fees + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + /// mpl-token-metadata collection metadata or mpl-asset collection + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Token account + pub token: &'b solana_program::account_info::AccountInfo<'a>, + /// Mint of token asset + pub mint: &'b solana_program::account_info::AccountInfo<'a>, + /// Metadata (pda of ['metadata', program id, mint id]) + pub metadata: &'b solana_program::account_info::AccountInfo<'a>, + /// Edition of token asset + pub edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Owner token record account + pub owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// SPL Token Program + pub spl_token_program: &'b solana_program::account_info::AccountInfo<'a>, + /// SPL Associated Token Account program + pub spl_ata_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Token Authorization Rules Program + pub authorization_rules_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Token Authorization Rules account + pub authorization_rules: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: MigrateInstructionArgs, +} + +impl<'a, 'b> MigrateCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: MigrateCpiAccounts<'a, 'b>, + args: MigrateInstructionArgs, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + owner: accounts.owner, + payer: accounts.payer, + collection: accounts.collection, + token: accounts.token, + mint: accounts.mint, + metadata: accounts.metadata, + edition: accounts.edition, + owner_token_record: accounts.owner_token_record, + spl_token_program: accounts.spl_token_program, + spl_ata_program: accounts.spl_ata_program, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + authorization_rules_program: accounts.authorization_rules_program, + authorization_rules: accounts.authorization_rules, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(15 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + true, + )); + if let Some(owner) = self.owner { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *owner.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.token.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.mint.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.metadata.key, + false, + )); + if let Some(edition) = self.edition { + accounts.push(solana_program::instruction::AccountMeta::new( + *edition.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(owner_token_record) = self.owner_token_record { + accounts.push(solana_program::instruction::AccountMeta::new( + *owner_token_record.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.spl_token_program.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.spl_ata_program.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(authorization_rules_program) = self.authorization_rules_program { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authorization_rules_program.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(authorization_rules) = self.authorization_rules { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *authorization_rules.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = MigrateInstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(15 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + if let Some(owner) = self.owner { + account_infos.push(owner.clone()); + } + account_infos.push(self.payer.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.token.clone()); + account_infos.push(self.mint.clone()); + account_infos.push(self.metadata.clone()); + if let Some(edition) = self.edition { + account_infos.push(edition.clone()); + } + if let Some(owner_token_record) = self.owner_token_record { + account_infos.push(owner_token_record.clone()); + } + account_infos.push(self.spl_token_program.clone()); + account_infos.push(self.spl_ata_program.clone()); + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + if let Some(authorization_rules_program) = self.authorization_rules_program { + account_infos.push(authorization_rules_program.clone()); + } + if let Some(authorization_rules) = self.authorization_rules { + account_infos.push(authorization_rules.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `migrate` CPI instruction builder. +pub struct MigrateCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> MigrateCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(MigrateCpiBuilderInstruction { + __program: program, + asset_address: None, + owner: None, + payer: None, + collection: None, + token: None, + mint: None, + metadata: None, + edition: None, + owner_token_record: None, + spl_token_program: None, + spl_ata_program: None, + system_program: None, + log_wrapper: None, + authorization_rules_program: None, + authorization_rules: None, + data_state: None, + level: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the new asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The authority of the new asset + #[inline(always)] + pub fn owner( + &mut self, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.owner = owner; + self + } + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + /// `[optional account]` + /// mpl-token-metadata collection metadata or mpl-asset collection + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// Token account + #[inline(always)] + pub fn token(&mut self, token: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.token = Some(token); + self + } + /// Mint of token asset + #[inline(always)] + pub fn mint(&mut self, mint: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.mint = Some(mint); + self + } + /// Metadata (pda of ['metadata', program id, mint id]) + #[inline(always)] + pub fn metadata( + &mut self, + metadata: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.metadata = Some(metadata); + self + } + /// `[optional account]` + /// Edition of token asset + #[inline(always)] + pub fn edition( + &mut self, + edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.edition = edition; + self + } + /// `[optional account]` + /// Owner token record account + #[inline(always)] + pub fn owner_token_record( + &mut self, + owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.owner_token_record = owner_token_record; + self + } + /// SPL Token Program + #[inline(always)] + pub fn spl_token_program( + &mut self, + spl_token_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.spl_token_program = Some(spl_token_program); + self + } + /// SPL Associated Token Account program + #[inline(always)] + pub fn spl_ata_program( + &mut self, + spl_ata_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.spl_ata_program = Some(spl_ata_program); + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// `[optional account]` + /// Token Authorization Rules Program + #[inline(always)] + pub fn authorization_rules_program( + &mut self, + authorization_rules_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authorization_rules_program = authorization_rules_program; + self + } + /// `[optional account]` + /// Token Authorization Rules account + #[inline(always)] + pub fn authorization_rules( + &mut self, + authorization_rules: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.authorization_rules = authorization_rules; + self + } + #[inline(always)] + pub fn data_state(&mut self, data_state: DataState) -> &mut Self { + self.instruction.data_state = Some(data_state); + self + } + #[inline(always)] + pub fn level(&mut self, level: MigrationLevel) -> &mut Self { + self.instruction.level = Some(level); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = MigrateInstructionArgs { + data_state: self + .instruction + .data_state + .clone() + .expect("data_state is not set"), + level: self.instruction.level.clone().expect("level is not set"), + }; + let instruction = MigrateCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + owner: self.instruction.owner, + + payer: self.instruction.payer.expect("payer is not set"), + + collection: self.instruction.collection, + + token: self.instruction.token.expect("token is not set"), + + mint: self.instruction.mint.expect("mint is not set"), + + metadata: self.instruction.metadata.expect("metadata is not set"), + + edition: self.instruction.edition, + + owner_token_record: self.instruction.owner_token_record, + + spl_token_program: self + .instruction + .spl_token_program + .expect("spl_token_program is not set"), + + spl_ata_program: self + .instruction + .spl_ata_program + .expect("spl_ata_program is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + + authorization_rules_program: self.instruction.authorization_rules_program, + + authorization_rules: self.instruction.authorization_rules, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct MigrateCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token: Option<&'b solana_program::account_info::AccountInfo<'a>>, + mint: Option<&'b solana_program::account_info::AccountInfo<'a>>, + metadata: Option<&'b solana_program::account_info::AccountInfo<'a>>, + edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, + spl_token_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + spl_ata_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authorization_rules_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authorization_rules: Option<&'b solana_program::account_info::AccountInfo<'a>>, + data_state: Option, + level: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/mod.rs b/clients/rust/src/generated/instructions/mod.rs index 1aa82527..0dc9250e 100644 --- a/clients/rust/src/generated/instructions/mod.rs +++ b/clients/rust/src/generated/instructions/mod.rs @@ -5,6 +5,24 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +pub(crate) mod burn; +pub(crate) mod compress; pub(crate) mod create; +pub(crate) mod decompress; +pub(crate) mod delegate; +pub(crate) mod freeze; +pub(crate) mod migrate; +pub(crate) mod thaw; +pub(crate) mod transfer; +pub(crate) mod update; +pub use self::burn::*; +pub use self::compress::*; pub use self::create::*; +pub use self::decompress::*; +pub use self::delegate::*; +pub use self::freeze::*; +pub use self::migrate::*; +pub use self::thaw::*; +pub use self::transfer::*; +pub use self::update::*; diff --git a/clients/rust/src/generated/instructions/thaw.rs b/clients/rust/src/generated/instructions/thaw.rs new file mode 100644 index 00000000..68d03eb4 --- /dev/null +++ b/clients/rust/src/generated/instructions/thaw.rs @@ -0,0 +1,372 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Thaw { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The delegate of the asset + pub delegate: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Thaw { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.delegate, + true, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = ThawInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct ThawInstructionData { + discriminator: u8, +} + +impl ThawInstructionData { + fn new() -> Self { + Self { discriminator: 7 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct ThawBuilder { + asset_address: Option, + delegate: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl ThawBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// The delegate of the asset + #[inline(always)] + pub fn delegate(&mut self, delegate: solana_program::pubkey::Pubkey) -> &mut Self { + self.delegate = Some(delegate); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Thaw { + asset_address: self.asset_address.expect("asset_address is not set"), + delegate: self.delegate.expect("delegate is not set"), + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `thaw` CPI accounts. +pub struct ThawCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The delegate of the asset + pub delegate: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `thaw` CPI instruction. +pub struct ThawCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The delegate of the asset + pub delegate: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> ThawCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: ThawCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + delegate: accounts.delegate, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.delegate.key, + true, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = ThawInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + account_infos.push(self.delegate.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `thaw` CPI instruction builder. +pub struct ThawCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> ThawCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(ThawCpiBuilderInstruction { + __program: program, + asset_address: None, + delegate: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// The delegate of the asset + #[inline(always)] + pub fn delegate( + &mut self, + delegate: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.delegate = Some(delegate); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = ThawCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + delegate: self.instruction.delegate.expect("delegate is not set"), + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct ThawCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/transfer.rs b/clients/rust/src/generated/instructions/transfer.rs new file mode 100644 index 00000000..1efc7618 --- /dev/null +++ b/clients/rust/src/generated/instructions/transfer.rs @@ -0,0 +1,514 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Transfer { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The collection to which the asset belongs + pub collection: Option, + /// The owner or delegate of the asset + pub authority: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: Option, + /// The new owner to which to transfer the asset + pub new_owner: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Transfer { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + collection, false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.authority, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.new_owner, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let data = TransferInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct TransferInstructionData { + discriminator: u8, +} + +impl TransferInstructionData { + fn new() -> Self { + Self { discriminator: 4 } + } +} + +/// Instruction builder. +#[derive(Default)] +pub struct TransferBuilder { + asset_address: Option, + collection: Option, + authority: Option, + payer: Option, + new_owner: Option, + log_wrapper: Option, + __remaining_accounts: Vec, +} + +impl TransferBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection(&mut self, collection: Option) -> &mut Self { + self.collection = collection; + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { + self.authority = Some(authority); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: Option) -> &mut Self { + self.payer = payer; + self + } + /// The new owner to which to transfer the asset + #[inline(always)] + pub fn new_owner(&mut self, new_owner: solana_program::pubkey::Pubkey) -> &mut Self { + self.new_owner = Some(new_owner); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Transfer { + asset_address: self.asset_address.expect("asset_address is not set"), + collection: self.collection, + authority: self.authority.expect("authority is not set"), + payer: self.payer, + new_owner: self.new_owner.expect("new_owner is not set"), + log_wrapper: self.log_wrapper, + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `transfer` CPI accounts. +pub struct TransferCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The owner or delegate of the asset + pub authority: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new owner to which to transfer the asset + pub new_owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `transfer` CPI instruction. +pub struct TransferCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The collection to which the asset belongs + pub collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The owner or delegate of the asset + pub authority: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new owner to which to transfer the asset + pub new_owner: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +impl<'a, 'b> TransferCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: TransferCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + collection: accounts.collection, + authority: accounts.authority, + payer: accounts.payer, + new_owner: accounts.new_owner, + log_wrapper: accounts.log_wrapper, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + if let Some(collection) = self.collection { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *collection.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.authority.key, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new( + *payer.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.new_owner.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let data = TransferInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + if let Some(collection) = self.collection { + account_infos.push(collection.clone()); + } + account_infos.push(self.authority.clone()); + if let Some(payer) = self.payer { + account_infos.push(payer.clone()); + } + account_infos.push(self.new_owner.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `transfer` CPI instruction builder. +pub struct TransferCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> TransferCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(TransferCpiBuilderInstruction { + __program: program, + asset_address: None, + collection: None, + authority: None, + payer: None, + new_owner: None, + log_wrapper: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// `[optional account]` + /// The collection to which the asset belongs + #[inline(always)] + pub fn collection( + &mut self, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.collection = collection; + self + } + /// The owner or delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.authority = Some(authority); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer( + &mut self, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.payer = payer; + self + } + /// The new owner to which to transfer the asset + #[inline(always)] + pub fn new_owner( + &mut self, + new_owner: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.new_owner = Some(new_owner); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let instruction = TransferCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + collection: self.instruction.collection, + + authority: self.instruction.authority.expect("authority is not set"), + + payer: self.instruction.payer, + + new_owner: self.instruction.new_owner.expect("new_owner is not set"), + + log_wrapper: self.instruction.log_wrapper, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct TransferCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + collection: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/instructions/update.rs b/clients/rust/src/generated/instructions/update.rs new file mode 100644 index 00000000..00e1b357 --- /dev/null +++ b/clients/rust/src/generated/instructions/update.rs @@ -0,0 +1,586 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +/// Accounts. +pub struct Update { + /// The address of the asset + pub asset_address: solana_program::pubkey::Pubkey, + /// The update authority or update authority delegate of the asset + pub authority: solana_program::pubkey::Pubkey, + /// The account paying for the storage fees + pub payer: Option, + /// The new update authority of the asset + pub new_update_authority: Option, + /// The system program + pub system_program: solana_program::pubkey::Pubkey, + /// The SPL Noop Program + pub log_wrapper: Option, +} + +impl Update { + pub fn instruction( + &self, + args: UpdateInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: UpdateInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.asset_address, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.authority, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new(payer, true)); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(new_update_authority) = self.new_update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + new_update_authority, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + log_wrapper, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.extend_from_slice(remaining_accounts); + let mut data = UpdateInstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +struct UpdateInstructionData { + discriminator: u8, +} + +impl UpdateInstructionData { + fn new() -> Self { + Self { discriminator: 5 } + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct UpdateInstructionArgs { + pub new_name: String, + pub new_uri: String, +} + +/// Instruction builder. +#[derive(Default)] +pub struct UpdateBuilder { + asset_address: Option, + authority: Option, + payer: Option, + new_update_authority: Option, + system_program: Option, + log_wrapper: Option, + new_name: Option, + new_uri: Option, + __remaining_accounts: Vec, +} + +impl UpdateBuilder { + pub fn new() -> Self { + Self::default() + } + /// The address of the asset + #[inline(always)] + pub fn asset_address(&mut self, asset_address: solana_program::pubkey::Pubkey) -> &mut Self { + self.asset_address = Some(asset_address); + self + } + /// The update authority or update authority delegate of the asset + #[inline(always)] + pub fn authority(&mut self, authority: solana_program::pubkey::Pubkey) -> &mut Self { + self.authority = Some(authority); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer(&mut self, payer: Option) -> &mut Self { + self.payer = payer; + self + } + /// `[optional account]` + /// The new update authority of the asset + #[inline(always)] + pub fn new_update_authority( + &mut self, + new_update_authority: Option, + ) -> &mut Self { + self.new_update_authority = new_update_authority; + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + /// The system program + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option, + ) -> &mut Self { + self.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn new_name(&mut self, new_name: String) -> &mut Self { + self.new_name = Some(new_name); + self + } + #[inline(always)] + pub fn new_uri(&mut self, new_uri: String) -> &mut Self { + self.new_uri = Some(new_uri); + self + } + /// Add an aditional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = Update { + asset_address: self.asset_address.expect("asset_address is not set"), + authority: self.authority.expect("authority is not set"), + payer: self.payer, + new_update_authority: self.new_update_authority, + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + log_wrapper: self.log_wrapper, + }; + let args = UpdateInstructionArgs { + new_name: self.new_name.clone().expect("new_name is not set"), + new_uri: self.new_uri.clone().expect("new_uri is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `update` CPI accounts. +pub struct UpdateCpiAccounts<'a, 'b> { + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or update authority delegate of the asset + pub authority: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new update authority of the asset + pub new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, +} + +/// `update` CPI instruction. +pub struct UpdateCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + /// The address of the asset + pub asset_address: &'b solana_program::account_info::AccountInfo<'a>, + /// The update authority or update authority delegate of the asset + pub authority: &'b solana_program::account_info::AccountInfo<'a>, + /// The account paying for the storage fees + pub payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The new update authority of the asset + pub new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The system program + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The SPL Noop Program + pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: UpdateInstructionArgs, +} + +impl<'a, 'b> UpdateCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: UpdateCpiAccounts<'a, 'b>, + args: UpdateInstructionArgs, + ) -> Self { + Self { + __program: program, + asset_address: accounts.asset_address, + authority: accounts.authority, + payer: accounts.payer, + new_update_authority: accounts.new_update_authority, + system_program: accounts.system_program, + log_wrapper: accounts.log_wrapper, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.asset_address.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.authority.key, + true, + )); + if let Some(payer) = self.payer { + accounts.push(solana_program::instruction::AccountMeta::new( + *payer.key, true, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + if let Some(new_update_authority) = self.new_update_authority { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *new_update_authority.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + if let Some(log_wrapper) = self.log_wrapper { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *log_wrapper.key, + false, + )); + } else { + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + crate::MPL_ASSET_ID, + false, + )); + } + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = UpdateInstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::MPL_ASSET_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.asset_address.clone()); + account_infos.push(self.authority.clone()); + if let Some(payer) = self.payer { + account_infos.push(payer.clone()); + } + if let Some(new_update_authority) = self.new_update_authority { + account_infos.push(new_update_authority.clone()); + } + account_infos.push(self.system_program.clone()); + if let Some(log_wrapper) = self.log_wrapper { + account_infos.push(log_wrapper.clone()); + } + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// `update` CPI instruction builder. +pub struct UpdateCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> UpdateCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(UpdateCpiBuilderInstruction { + __program: program, + asset_address: None, + authority: None, + payer: None, + new_update_authority: None, + system_program: None, + log_wrapper: None, + new_name: None, + new_uri: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + /// The address of the asset + #[inline(always)] + pub fn asset_address( + &mut self, + asset_address: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.asset_address = Some(asset_address); + self + } + /// The update authority or update authority delegate of the asset + #[inline(always)] + pub fn authority( + &mut self, + authority: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.authority = Some(authority); + self + } + /// `[optional account]` + /// The account paying for the storage fees + #[inline(always)] + pub fn payer( + &mut self, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.payer = payer; + self + } + /// `[optional account]` + /// The new update authority of the asset + #[inline(always)] + pub fn new_update_authority( + &mut self, + new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.new_update_authority = new_update_authority; + self + } + /// The system program + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + /// `[optional account]` + /// The SPL Noop Program + #[inline(always)] + pub fn log_wrapper( + &mut self, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ) -> &mut Self { + self.instruction.log_wrapper = log_wrapper; + self + } + #[inline(always)] + pub fn new_name(&mut self, new_name: String) -> &mut Self { + self.instruction.new_name = Some(new_name); + self + } + #[inline(always)] + pub fn new_uri(&mut self, new_uri: String) -> &mut Self { + self.instruction.new_uri = Some(new_uri); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = UpdateInstructionArgs { + new_name: self + .instruction + .new_name + .clone() + .expect("new_name is not set"), + new_uri: self + .instruction + .new_uri + .clone() + .expect("new_uri is not set"), + }; + let instruction = UpdateCpi { + __program: self.instruction.__program, + + asset_address: self + .instruction + .asset_address + .expect("asset_address is not set"), + + authority: self.instruction.authority.expect("authority is not set"), + + payer: self.instruction.payer, + + new_update_authority: self.instruction.new_update_authority, + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + + log_wrapper: self.instruction.log_wrapper, + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +struct UpdateCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + asset_address: Option<&'b solana_program::account_info::AccountInfo<'a>>, + authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_update_authority: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_name: Option, + new_uri: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/src/generated/types/authority.rs b/clients/rust/src/generated/types/authority.rs index 56fe2936..42db0ebc 100644 --- a/clients/rust/src/generated/types/authority.rs +++ b/clients/rust/src/generated/types/authority.rs @@ -24,4 +24,5 @@ pub enum Authority { SameAs { plugin: Plugin, }, + Collection, } diff --git a/clients/rust/src/generated/types/collection.rs b/clients/rust/src/generated/types/collection.rs new file mode 100644 index 00000000..fca0f188 --- /dev/null +++ b/clients/rust/src/generated/types/collection.rs @@ -0,0 +1,21 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Collection { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub collection_address: Pubkey, + pub required: bool, +} diff --git a/clients/rust/src/generated/types/compression_proof.rs b/clients/rust/src/generated/types/compression_proof.rs new file mode 100644 index 00000000..bd2a01ee --- /dev/null +++ b/clients/rust/src/generated/types/compression_proof.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CompressionProof {} diff --git a/clients/rust/src/generated/types/extra_accounts.rs b/clients/rust/src/generated/types/extra_accounts.rs new file mode 100644 index 00000000..73ebf3f7 --- /dev/null +++ b/clients/rust/src/generated/types/extra_accounts.rs @@ -0,0 +1,28 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ExtraAccounts { + None, + SplHook { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + extra_account_metas: Pubkey, + }, + MplHook { + mint_pda: Option, + collection_pda: Option, + owner_pda: Option, + }, +} diff --git a/clients/rust/src/generated/types/migration_level.rs b/clients/rust/src/generated/types/migration_level.rs new file mode 100644 index 00000000..15e7a5c3 --- /dev/null +++ b/clients/rust/src/generated/types/migration_level.rs @@ -0,0 +1,16 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index a984daf3..b24b2c4e 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -5,18 +5,30 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -pub(crate) mod asset_header; pub(crate) mod authority; +pub(crate) mod collection; +pub(crate) mod compression_proof; pub(crate) mod creator; pub(crate) mod data_state; +pub(crate) mod extra_accounts; pub(crate) mod key; +pub(crate) mod migration_level; pub(crate) mod plugin; +pub(crate) mod plugin_header; +pub(crate) mod plugin_registry; +pub(crate) mod registry_data; pub(crate) mod royalties; -pub use self::asset_header::*; pub use self::authority::*; +pub use self::collection::*; +pub use self::compression_proof::*; pub use self::creator::*; pub use self::data_state::*; +pub use self::extra_accounts::*; pub use self::key::*; +pub use self::migration_level::*; pub use self::plugin::*; +pub use self::plugin_header::*; +pub use self::plugin_registry::*; +pub use self::registry_data::*; pub use self::royalties::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 4fabc30c..e0a2fff5 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -12,8 +12,6 @@ use borsh::BorshSerialize; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Plugin { Reserved, - Asset, - HashedAsset, Royalties, MasterEdition, PrintEdition, diff --git a/clients/rust/src/generated/types/asset_header.rs b/clients/rust/src/generated/types/plugin_header.rs similarity index 94% rename from clients/rust/src/generated/types/asset_header.rs rename to clients/rust/src/generated/types/plugin_header.rs index 9eab418b..cd7b8e00 100644 --- a/clients/rust/src/generated/types/asset_header.rs +++ b/clients/rust/src/generated/types/plugin_header.rs @@ -10,7 +10,7 @@ use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct AssetHeader { +pub struct PluginHeader { pub version: u8, pub plugin_map_offset: u64, } diff --git a/clients/rust/src/generated/types/plugin_registry.rs b/clients/rust/src/generated/types/plugin_registry.rs new file mode 100644 index 00000000..60661820 --- /dev/null +++ b/clients/rust/src/generated/types/plugin_registry.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::Key; +use crate::generated::types::RegistryData; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PluginRegistry { + pub registry: Vec<(Key, RegistryData)>, +} diff --git a/clients/rust/src/generated/types/registry_data.rs b/clients/rust/src/generated/types/registry_data.rs new file mode 100644 index 00000000..56b2f87a --- /dev/null +++ b/clients/rust/src/generated/types/registry_data.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::Authority; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RegistryData { + pub offset: u64, + pub authorities: Vec, +} diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index 6e2e237c..94919eb6 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -13,30 +13,657 @@ "The address of the new asset" ] }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "updateAuthority", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The authority of the new asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "owner", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The owner of the new asset. Defaults to the authority if not present." + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "createArgs", + "type": { + "defined": "CreateArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 0 + } + }, + { + "name": "Migrate", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": true, + "docs": [ + "The address of the new asset" + ] + }, + { + "name": "owner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "The authority of the new asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "mpl-token-metadata collection metadata or mpl-asset collection" + ] + }, + { + "name": "token", + "isMut": true, + "isSigner": false, + "docs": [ + "Token account" + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Mint of token asset" + ] + }, + { + "name": "metadata", + "isMut": true, + "isSigner": false, + "docs": [ + "Metadata (pda of ['metadata', program id, mint id])" + ] + }, + { + "name": "edition", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "Edition of token asset" + ] + }, + { + "name": "ownerTokenRecord", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "Owner token record account" + ] + }, + { + "name": "splTokenProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "SPL Token Program" + ] + }, + { + "name": "splAtaProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "SPL Associated Token Account program" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + }, + { + "name": "authorizationRulesProgram", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Token Authorization Rules Program" + ] + }, + { + "name": "authorizationRules", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Token Authorization Rules account" + ] + } + ], + "args": [ + { + "name": "migrateArgs", + "type": { + "defined": "MigrateArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 1 + } + }, + { + "name": "Delegate", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "owner", + "isMut": true, + "isSigner": true, + "docs": [ + "The owner of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "isOptional": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "delegate", + "isMut": false, + "isSigner": false, + "docs": [ + "The new simple delegate for the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "delegateArgs", + "type": { + "defined": "DelegateArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 2 + } + }, + { + "name": "Burn", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": true, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "isOptional": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "burnArgs", + "type": { + "defined": "BurnArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 3 + } + }, + { + "name": "Transfer", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "collection", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The collection to which the asset belongs" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "isOptional": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "newOwner", + "isMut": false, + "isSigner": false, + "docs": [ + "The new owner to which to transfer the asset" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "transferArgs", + "type": { + "defined": "TransferArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 4 + } + }, + { + "name": "Update", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, { "name": "authority", "isMut": false, + "isSigner": true, + "docs": [ + "The update authority or update authority delegate of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "isOptional": true, + "docs": [ + "The account paying for the storage fees" + ] + }, + { + "name": "newUpdateAuthority", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The new update authority of the asset" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "updateArgs", + "type": { + "defined": "UpdateArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 5 + } + }, + { + "name": "Freeze", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "delegate", + "isMut": false, + "isSigner": true, + "docs": [ + "The delegate of the asset" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "freezeArgs", + "type": { + "defined": "FreezeArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 6 + } + }, + { + "name": "Thaw", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "delegate", + "isMut": false, + "isSigner": true, + "docs": [ + "The delegate of the asset" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "thawArgs", + "type": { + "defined": "ThawArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 7 + } + }, + { + "name": "Compress", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, + "isSigner": false, + "docs": [ + "The address of the asset" + ] + }, + { + "name": "owner", + "isMut": false, + "isSigner": true, + "docs": [ + "The owner or delegate of the asset" + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true, + "isOptional": true, + "docs": [ + "The account receiving the storage fees" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The system program" + ] + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "The SPL Noop Program" + ] + } + ], + "args": [ + { + "name": "compressArgs", + "type": { + "defined": "CompressArgs" + } + } + ], + "discriminant": { + "type": "u8", + "value": 8 + } + }, + { + "name": "Decompress", + "accounts": [ + { + "name": "assetAddress", + "isMut": true, "isSigner": false, - "isOptional": true, "docs": [ - "The authority of the new asset" + "The address of the asset" ] }, { - "name": "payer", - "isMut": true, + "name": "owner", + "isMut": false, "isSigner": true, "docs": [ - "The account paying for the storage fees" + "The owner or delegate of the asset" ] }, { - "name": "owner", - "isMut": false, - "isSigner": false, + "name": "payer", + "isMut": true, + "isSigner": true, "isOptional": true, "docs": [ - "The owner of the new asset. Defaults to the authority if not present." + "The account paying for the storage fees" ] }, { @@ -59,15 +686,15 @@ ], "args": [ { - "name": "createArgs", + "name": "decompressArgs", "type": { - "defined": "CreateArgs" + "defined": "DecompressArgs" } } ], "discriminant": { "type": "u8", - "value": 0 + "value": 9 } } ], @@ -121,12 +748,6 @@ 32 ] } - }, - { - "name": "watermarkSlot", - "type": { - "option": "u64" - } } ] } @@ -134,27 +755,60 @@ ], "types": [ { - "name": "CreateArgs", + "name": "RegistryData", "type": { "kind": "struct", "fields": [ { - "name": "dataState", + "name": "offset", + "type": "u64" + }, + { + "name": "authorities", "type": { - "defined": "DataState" + "vec": { + "defined": "Authority" + } } - }, + } + ] + } + }, + { + "name": "PluginRegistry", + "type": { + "kind": "struct", + "fields": [ { - "name": "watermark", - "type": "bool" - }, + "name": "registry", + "type": { + "vec": { + "tuple": [ + { + "defined": "Key" + }, + { + "defined": "RegistryData" + } + ] + } + } + } + ] + } + }, + { + "name": "Collection", + "type": { + "kind": "struct", + "fields": [ { - "name": "name", - "type": "string" + "name": "collectionAddress", + "type": "publicKey" }, { - "name": "uri", - "type": "string" + "name": "required", + "type": "bool" } ] } @@ -200,72 +854,157 @@ } }, { - "name": "AssetHeader", + "name": "CompressionProof", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "BurnArgs", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "CompressArgs", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "CreateArgs", "type": { "kind": "struct", "fields": [ { - "name": "version", - "type": "u8" + "name": "dataState", + "type": { + "defined": "DataState" + } }, { - "name": "pluginMapOffset", - "type": "u64" + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" } ] } }, { - "name": "Plugin", + "name": "DecompressArgs", "type": { - "kind": "enum", - "variants": [ - { - "name": "Reserved" - }, - { - "name": "Asset" - }, + "kind": "struct", + "fields": [] + } + }, + { + "name": "DelegateArgs", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "FreezeArgs", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "MigrateArgs", + "type": { + "kind": "struct", + "fields": [ { - "name": "HashedAsset" + "name": "dataState", + "type": { + "defined": "DataState" + } }, { - "name": "Royalties" - }, + "name": "level", + "type": { + "defined": "MigrationLevel" + } + } + ] + } + }, + { + "name": "ThawArgs", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "TransferArgs", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "UpdateArgs", + "type": { + "kind": "struct", + "fields": [ { - "name": "MasterEdition" + "name": "newName", + "type": "string" }, { - "name": "PrintEdition" - }, + "name": "newUri", + "type": "string" + } + ] + } + }, + { + "name": "PluginHeader", + "type": { + "kind": "struct", + "fields": [ { - "name": "Delegate" + "name": "version", + "type": "u8" }, { - "name": "Inscription" + "name": "pluginMapOffset", + "type": "u64" } ] } }, { - "name": "Key", + "name": "Plugin", "type": { "kind": "enum", "variants": [ { - "name": "Uninitialized" + "name": "Reserved" }, { - "name": "Asset" + "name": "Royalties" }, { - "name": "HashedAsset" + "name": "MasterEdition" }, { - "name": "Collection" + "name": "PrintEdition" }, { - "name": "HashedCollection" + "name": "Delegate" + }, + { + "name": "Inscription" } ] } @@ -311,6 +1050,89 @@ } } ] + }, + { + "name": "Collection" + } + ] + } + }, + { + "name": "ExtraAccounts", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "SplHook", + "fields": [ + { + "name": "extra_account_metas", + "type": "publicKey" + } + ] + }, + { + "name": "MplHook", + "fields": [ + { + "name": "mint_pda", + "type": { + "option": "publicKey" + } + }, + { + "name": "collection_pda", + "type": { + "option": "publicKey" + } + }, + { + "name": "owner_pda", + "type": { + "option": "publicKey" + } + } + ] + } + ] + } + }, + { + "name": "Key", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Uninitialized" + }, + { + "name": "Asset" + }, + { + "name": "HashedAsset" + }, + { + "name": "Collection" + }, + { + "name": "HashedCollection" + } + ] + } + }, + { + "name": "MigrationLevel", + "type": { + "kind": "enum", + "variants": [ + { + "name": "MigrateOnly" + }, + { + "name": "MigrateAndBurn" } ] } @@ -331,6 +1153,16 @@ "code": 2, "name": "SerializationError", "msg": "Error serializing account" + }, + { + "code": 3, + "name": "PluginsNotInitialized", + "msg": "Plugins not initialized" + }, + { + "code": 4, + "name": "PluginNotFound", + "msg": "Plugin not found" } ], "metadata": { diff --git a/programs/mpl-asset/src/instruction.rs b/programs/mpl-asset/src/instruction.rs index 7a02274b..646838ac 100644 --- a/programs/mpl-asset/src/instruction.rs +++ b/programs/mpl-asset/src/instruction.rs @@ -47,7 +47,8 @@ pub enum MplAssetInstruction { #[account(2, writable, signer, name="owner", desc = "The owner of the asset")] #[account(3, optional, writable, signer, name="payer", desc = "The account paying for the storage fees")] #[account(4, name="delegate", desc = "The new simple delegate for the asset")] - #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] + #[account(5, name="system_program", desc = "The system program")] + #[account(6, optional, name="log_wrapper", desc = "The SPL Noop Program")] Delegate(DelegateArgs), //TODO: Implement this instruction diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index 51ba983b..618b62c7 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -14,6 +14,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use crate::{ error::MplAssetError, state::{Authority, Key}, + utils::DataBlob, }; // macro_rules! plugin_instruction { @@ -50,8 +51,7 @@ pub struct RegistryData { #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct PluginRegistry { - pub registry: Vec<(Key, RegistryData)>, - // pub third_party_registry: HashMap, + pub registry: Vec<(Key, RegistryData)>, // 4 } impl PluginRegistry { @@ -71,6 +71,16 @@ impl PluginRegistry { } } +impl DataBlob for PluginRegistry { + fn get_initial_size() -> usize { + 4 + } + + fn get_size(&self) -> usize { + 4 //TODO: Fix this + } +} + // pub trait PluginTrait // where // Self: BorshSerialize + BorshDeserialize + Clone + std::fmt::Debug + Sized, diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 23bc8217..111e72f1 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -1,22 +1,27 @@ use borsh::BorshDeserialize; +use mpl_utils::resize_or_reallocate_account_raw; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, }; use crate::{ error::MplAssetError, - state::{Asset, Key, PluginHeader}, + state::{Asset, Authority, Key, PluginHeader}, utils::DataBlob, }; use super::{Plugin, PluginRegistry}; -//TODO:keith: Implement this function /// Create plugin header and registry if it doesn't exist -pub fn create_idempotent(account: &AccountInfo) -> ProgramResult { - let mut bytes: &[u8] = &(*account.data).borrow(); - let asset = Asset::deserialize(&mut bytes)?; +pub fn create_idempotent<'a>( + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + let asset = { + let mut bytes: &[u8] = &(*account.data).borrow(); + Asset::deserialize(&mut bytes)? + }; // Check if the plugin header and registry exist. if asset.get_size() == account.data_len() { @@ -27,6 +32,13 @@ pub fn create_idempotent(account: &AccountInfo) -> ProgramResult { }; let registry = PluginRegistry { registry: vec![] }; + resize_or_reallocate_account_raw( + account, + payer, + system_program, + header.plugin_map_offset + PluginRegistry::get_initial_size(), + )?; + header.save(account, asset.get_size())?; registry.save(account, header.plugin_map_offset)?; } @@ -45,33 +57,38 @@ pub fn assert_plugins_initialized(account: &AccountInfo) -> ProgramResult { Ok(()) } -//TODO:keith: Implement this function +/// Fetch the plugin from the registry. pub fn fetch_plugin( account: &AccountInfo, - plugin: Key, -) -> Result<((Vec, Plugin)), ProgramError> { - // Fetch the plugin from the registry. + key: Key, +) -> Result<(Vec, Plugin), ProgramError> { let mut bytes: &[u8] = &(*account.data).borrow(); let asset = Asset::deserialize(&mut bytes)?; let header = PluginHeader::load(account, asset.get_size())?; let PluginRegistry { registry } = PluginRegistry::load(account, header.plugin_map_offset)?; + // Find the plugin in the registry. let plugin_data = registry .iter() - .find(|(key, _)| *key == plugin) + .find(|(plugin_key, _)| *plugin_key == key) .map(|(_, data)| data) .ok_or(MplAssetError::PluginNotFound)?; - let authorities = plugin_data - .authorities - .iter() - .map(|authority| authority.key) - .collect(); + // Deserialize the plugin. + let plugin = Plugin::deserialize(&mut &(*account.data).borrow()[plugin_data.offset..])?; + + // Return the plugin and its authorities. + Ok((plugin_data.authorities.clone(), plugin)) } -//TODO:keith: Implement this function -pub fn list_plugins() -> Vec { - // Create plugin header and registry if it doesn't exist - vec![] +/// Create plugin header and registry if it doesn't exist +pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { + let mut bytes: &[u8] = &(*account.data).borrow(); + let asset = Asset::deserialize(&mut bytes)?; + + let header = PluginHeader::load(account, asset.get_size())?; + let PluginRegistry { registry } = PluginRegistry::load(account, header.plugin_map_offset)?; + + Ok(registry.iter().map(|(key, _)| *key).collect()) } diff --git a/programs/mpl-asset/src/processor/burn.rs b/programs/mpl-asset/src/processor/burn.rs index f3e8eca7..1830537c 100644 --- a/programs/mpl-asset/src/processor/burn.rs +++ b/programs/mpl-asset/src/processor/burn.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct BurnArgs {} diff --git a/programs/mpl-asset/src/processor/compress.rs b/programs/mpl-asset/src/processor/compress.rs index d424241c..e1a05abc 100644 --- a/programs/mpl-asset/src/processor/compress.rs +++ b/programs/mpl-asset/src/processor/compress.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct CompressArgs {} diff --git a/programs/mpl-asset/src/processor/decompress.rs b/programs/mpl-asset/src/processor/decompress.rs index 92f90ad9..efe12bf3 100644 --- a/programs/mpl-asset/src/processor/decompress.rs +++ b/programs/mpl-asset/src/processor/decompress.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct DecompressArgs {} diff --git a/programs/mpl-asset/src/processor/delegate.rs b/programs/mpl-asset/src/processor/delegate.rs index 071e7884..97b2b448 100644 --- a/programs/mpl-asset/src/processor/delegate.rs +++ b/programs/mpl-asset/src/processor/delegate.rs @@ -7,21 +7,23 @@ use solana_program::{ use crate::{ error::MplAssetError, - instruction::accounts::CreateAccounts, + instruction::accounts::{CreateAccounts, DelegateAccounts}, + plugins::create_idempotent, state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct DelegateArgs {} pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], args: DelegateArgs) -> ProgramResult { + let ctx = DelegateAccounts::context(accounts)?; + + create_idempotent( + ctx.accounts.asset_address, + ctx.accounts.owner, + ctx.accounts.system_program, + )?; + Ok(()) } diff --git a/programs/mpl-asset/src/processor/freeze.rs b/programs/mpl-asset/src/processor/freeze.rs index b8f41a27..c1b88d35 100644 --- a/programs/mpl-asset/src/processor/freeze.rs +++ b/programs/mpl-asset/src/processor/freeze.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct FreezeArgs {} diff --git a/programs/mpl-asset/src/processor/migrate.rs b/programs/mpl-asset/src/processor/migrate.rs index 8d09e8be..9fc24dc1 100644 --- a/programs/mpl-asset/src/processor/migrate.rs +++ b/programs/mpl-asset/src/processor/migrate.rs @@ -8,16 +8,9 @@ use solana_program::{ use crate::{ error::MplAssetError, instruction::accounts::CreateAccounts, - state::{Asset, Compressible, DataState, HashedAsset, Key}, + state::{Asset, Compressible, DataState, HashedAsset, Key, MigrationLevel}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct MigrateArgs { diff --git a/programs/mpl-asset/src/processor/thaw.rs b/programs/mpl-asset/src/processor/thaw.rs index 235dd0db..68885e3c 100644 --- a/programs/mpl-asset/src/processor/thaw.rs +++ b/programs/mpl-asset/src/processor/thaw.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct ThawArgs {} diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 1a7edcf6..82812039 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct TransferArgs {} diff --git a/programs/mpl-asset/src/processor/update.rs b/programs/mpl-asset/src/processor/update.rs index 8f223a17..36684bdd 100644 --- a/programs/mpl-asset/src/processor/update.rs +++ b/programs/mpl-asset/src/processor/update.rs @@ -11,13 +11,6 @@ use crate::{ state::{Asset, Compressible, DataState, HashedAsset, Key}, }; -#[repr(C)] -#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] -pub enum MigrationLevel { - MigrateOnly, - MigrateAndBurn, -} - #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct UpdateArgs { diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 1b398b07..a5ffb93d 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -44,7 +44,7 @@ pub trait Compressible { fn hash(&self) -> Result<[u8; 32], ProgramError>; } -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub enum Key { Uninitialized, Asset, @@ -52,3 +52,10 @@ pub enum Key { Collection, HashedCollection, } + +#[repr(C)] +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] +pub enum MigrationLevel { + MigrateOnly, + MigrateAndBurn, +} From d72bb2ae9c141815f05b45bafef6d66a9c9e3e46 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:27:08 -0800 Subject: [PATCH 10/20] Minor cleanup on transfer --- README.md | 5 ++++ programs/mpl-asset/src/processor/transfer.rs | 25 +++++++------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 31a06d88..b456a533 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,8 @@ This project contains the following clients: ## Contributing Check out the [Contributing Guide](./CONTRIBUTING.md) the learn more about how to contribute to this project. + +TODO: +Today: Functional Asset - utilities, transfer, burn + JS tests, stretch goal compress/decompress +Tomorrow: Migrate, Plugins (Collection and Delgate which contains Freeze) + JS tests, compress/decompress, royalties +Next day: Lifecycle hooks with Pick 3 PDAs + JS tests diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 9d4562e0..6a8cf5e5 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -1,9 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, program::invoke}; use crate::{ error::MplAssetError, @@ -28,8 +25,9 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) assert_signer(payer)?; } - let serialized_data = match load_key(ctx.accounts.asset_address, 0)? { + match load_key(ctx.accounts.asset_address, 0)? { Key::HashedAsset => { + // TODO: Needs to be in helper. // Check that arguments passed in result in on-chain hash. let mut asset = Asset::from(args.compression_proof); let args_asset_hash = asset.hash()?; @@ -38,27 +36,20 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) return Err(MplAssetError::IncorrectAssetHash.into()); } + // TODO: Needs to be in helper. // Update owner and send Noop instruction. asset.owner = *ctx.accounts.new_owner.key; let serialized_data = asset.try_to_vec()?; invoke(&spl_noop::instruction(serialized_data), &[])?; // Make a new hashed asset with updated owner. - HashedAsset::new(asset.hash()?).try_to_vec()? + HashedAsset::new(asset.hash()?).save(ctx.accounts.asset_address, 0) } Key::Asset => { let mut asset = Asset::load(ctx.accounts.asset_address, 0)?; asset.owner = *ctx.accounts.new_owner.key; - asset.try_to_vec()? + asset.save(ctx.accounts.asset_address, 0) } - _ => return Err(MplAssetError::IncorrectAccount.into()), - }; - - sol_memcpy( - &mut ctx.accounts.asset_address.try_borrow_mut_data()?, - &serialized_data, - serialized_data.len(), - ); - - Ok(()) + _ => Err(MplAssetError::IncorrectAccount.into()), + } } From 95df253f14cb918c87f1ccc86ebfd3d699314ae0 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:36:26 -0800 Subject: [PATCH 11/20] Regenerate clients --- clients/js/src/generated/errors/mplAsset.ts | 26 +++++ .../js/src/generated/instructions/transfer.ts | 36 ++++-- .../src/generated/types/compressionProof.ts | 38 +++++- clients/js/src/generated/types/delegate.ts | 19 +++ clients/js/src/generated/types/index.ts | 1 + clients/js/src/generated/types/key.ts | 2 + clients/js/src/generated/types/plugin.ts | 89 ++++++++++++--- .../js/src/generated/types/pluginHeader.ts | 14 +-- .../js/src/generated/types/pluginRegistry.ts | 16 ++- .../rust/src/generated/errors/mpl_asset.rs | 6 + .../src/generated/instructions/transfer.rs | 56 ++++++++- .../src/generated/types/compression_proof.rs | 18 ++- clients/rust/src/generated/types/delegate.rs | 15 +++ clients/rust/src/generated/types/key.rs | 2 + clients/rust/src/generated/types/mod.rs | 2 + clients/rust/src/generated/types/plugin.rs | 5 +- .../rust/src/generated/types/plugin_header.rs | 3 +- .../src/generated/types/plugin_registry.rs | 3 + idls/mpl_asset_program.json | 108 ++++++++++++++++-- 19 files changed, 402 insertions(+), 57 deletions(-) create mode 100644 clients/js/src/generated/types/delegate.ts create mode 100644 clients/rust/src/generated/types/delegate.rs diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index ba8e3aa1..71538e57 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -80,6 +80,32 @@ export class PluginNotFoundError extends ProgramError { codeToErrorMap.set(0x4, PluginNotFoundError); nameToErrorMap.set('PluginNotFound', PluginNotFoundError); +/** IncorrectAccount: Incorrect account */ +export class IncorrectAccountError extends ProgramError { + readonly name: string = 'IncorrectAccount'; + + readonly code: number = 0x5; // 5 + + constructor(program: Program, cause?: Error) { + super('Incorrect account', program, cause); + } +} +codeToErrorMap.set(0x5, IncorrectAccountError); +nameToErrorMap.set('IncorrectAccount', IncorrectAccountError); + +/** IncorrectAssetHash: Incorrect asset hash */ +export class IncorrectAssetHashError extends ProgramError { + readonly name: string = 'IncorrectAssetHash'; + + readonly code: number = 0x6; // 6 + + constructor(program: Program, cause?: Error) { + super('Incorrect asset hash', program, cause); + } +} +codeToErrorMap.set(0x6, IncorrectAssetHashError); +nameToErrorMap.set('IncorrectAssetHash', IncorrectAssetHashError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/transfer.ts b/clients/js/src/generated/instructions/transfer.ts index bc60e6e2..4972eb05 100644 --- a/clients/js/src/generated/instructions/transfer.ts +++ b/clients/js/src/generated/instructions/transfer.ts @@ -25,6 +25,11 @@ import { ResolvedAccountsWithIndices, getAccountMetasAndSigners, } from '../shared'; +import { + CompressionProof, + CompressionProofArgs, + getCompressionProofSerializer, +} from '../types'; // Accounts. export type TransferInstructionAccounts = { @@ -43,9 +48,14 @@ export type TransferInstructionAccounts = { }; // Data. -export type TransferInstructionData = { discriminator: number }; +export type TransferInstructionData = { + discriminator: number; + compressionProof: CompressionProof; +}; -export type TransferInstructionDataArgs = {}; +export type TransferInstructionDataArgs = { + compressionProof: CompressionProofArgs; +}; export function getTransferInstructionDataSerializer(): Serializer< TransferInstructionDataArgs, @@ -56,17 +66,24 @@ export function getTransferInstructionDataSerializer(): Serializer< any, TransferInstructionData >( - struct([['discriminator', u8()]], { - description: 'TransferInstructionData', - }), + struct( + [ + ['discriminator', u8()], + ['compressionProof', getCompressionProofSerializer()], + ], + { description: 'TransferInstructionData' } + ), (value) => ({ ...value, discriminator: 4 }) ) as Serializer; } +// Args. +export type TransferInstructionArgs = TransferInstructionDataArgs; + // Instruction. export function transfer( context: Pick, - input: TransferInstructionAccounts + input: TransferInstructionAccounts & TransferInstructionArgs ): TransactionBuilder { // Program ID. const programId = context.programs.getPublicKey( @@ -96,6 +113,9 @@ export function transfer( }, }; + // Arguments. + const resolvedArgs: TransferInstructionArgs = { ...input }; + // Default values. if (!resolvedAccounts.authority.value) { resolvedAccounts.authority.value = context.identity; @@ -114,7 +134,9 @@ export function transfer( ); // Data. - const data = getTransferInstructionDataSerializer().serialize({}); + const data = getTransferInstructionDataSerializer().serialize( + resolvedArgs as TransferInstructionDataArgs + ); // Bytes Created On Chain. const bytesCreatedOnChain = 0; diff --git a/clients/js/src/generated/types/compressionProof.ts b/clients/js/src/generated/types/compressionProof.ts index a22a1f1c..8b706b09 100644 --- a/clients/js/src/generated/types/compressionProof.ts +++ b/clients/js/src/generated/types/compressionProof.ts @@ -6,17 +6,43 @@ * @see https://github.com/metaplex-foundation/kinobi */ -import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; +import { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + publicKey as publicKeySerializer, + string, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '.'; -export type CompressionProof = {}; +export type CompressionProof = { + key: Key; + updateAuthority: PublicKey; + owner: PublicKey; + name: string; + uri: string; +}; -export type CompressionProofArgs = CompressionProof; +export type CompressionProofArgs = { + key: KeyArgs; + updateAuthority: PublicKey; + owner: PublicKey; + name: string; + uri: string; +}; export function getCompressionProofSerializer(): Serializer< CompressionProofArgs, CompressionProof > { - return struct([], { - description: 'CompressionProof', - }) as Serializer; + return struct( + [ + ['key', getKeySerializer()], + ['updateAuthority', publicKeySerializer()], + ['owner', publicKeySerializer()], + ['name', string()], + ['uri', string()], + ], + { description: 'CompressionProof' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/delegate.ts b/clients/js/src/generated/types/delegate.ts new file mode 100644 index 00000000..d775b8ce --- /dev/null +++ b/clients/js/src/generated/types/delegate.ts @@ -0,0 +1,19 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, bool, struct } from '@metaplex-foundation/umi/serializers'; + +export type Delegate = { frozen: boolean }; + +export type DelegateArgs = Delegate; + +export function getDelegateSerializer(): Serializer { + return struct([['frozen', bool()]], { + description: 'Delegate', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 95aff73f..b5c2dccd 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -11,6 +11,7 @@ export * from './collection'; export * from './compressionProof'; export * from './creator'; export * from './dataState'; +export * from './delegate'; export * from './extraAccounts'; export * from './key'; export * from './migrationLevel'; diff --git a/clients/js/src/generated/types/key.ts b/clients/js/src/generated/types/key.ts index 5ec97358..169a0952 100644 --- a/clients/js/src/generated/types/key.ts +++ b/clients/js/src/generated/types/key.ts @@ -14,6 +14,8 @@ export enum Key { HashedAsset, Collection, HashedCollection, + PluginHeader, + PluginRegistry, } export type KeyArgs = Key; diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index a0130bcb..bc7fa052 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -6,22 +6,83 @@ * @see https://github.com/metaplex-foundation/kinobi */ -import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + struct, + tuple, + unit, +} from '@metaplex-foundation/umi/serializers'; +import { Delegate, DelegateArgs, getDelegateSerializer } from '.'; -export enum Plugin { - Reserved, - Royalties, - MasterEdition, - PrintEdition, - Delegate, - Inscription, -} +export type Plugin = + | { __kind: 'Reserved' } + | { __kind: 'Royalties' } + | { __kind: 'MasterEdition' } + | { __kind: 'PrintEdition' } + | { __kind: 'Delegate'; fields: [Delegate] } + | { __kind: 'Inscription' }; -export type PluginArgs = Plugin; +export type PluginArgs = + | { __kind: 'Reserved' } + | { __kind: 'Royalties' } + | { __kind: 'MasterEdition' } + | { __kind: 'PrintEdition' } + | { __kind: 'Delegate'; fields: [DelegateArgs] } + | { __kind: 'Inscription' }; export function getPluginSerializer(): Serializer { - return scalarEnum(Plugin, { description: 'Plugin' }) as Serializer< - PluginArgs, - Plugin - >; + return dataEnum( + [ + ['Reserved', unit()], + ['Royalties', unit()], + ['MasterEdition', unit()], + ['PrintEdition', unit()], + [ + 'Delegate', + struct>([ + ['fields', tuple([getDelegateSerializer()])], + ]), + ], + ['Inscription', unit()], + ], + { description: 'Plugin' } + ) as Serializer; +} + +// Data Enum Helpers. +export function plugin( + kind: 'Reserved' +): GetDataEnumKind; +export function plugin( + kind: 'Royalties' +): GetDataEnumKind; +export function plugin( + kind: 'MasterEdition' +): GetDataEnumKind; +export function plugin( + kind: 'PrintEdition' +): GetDataEnumKind; +export function plugin( + kind: 'Delegate', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function plugin( + kind: 'Inscription' +): GetDataEnumKind; +export function plugin( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isPlugin( + kind: K, + value: Plugin +): value is Plugin & { __kind: K } { + return value.__kind === kind; } diff --git a/clients/js/src/generated/types/pluginHeader.ts b/clients/js/src/generated/types/pluginHeader.ts index 7bd9bf77..696a2ee9 100644 --- a/clients/js/src/generated/types/pluginHeader.ts +++ b/clients/js/src/generated/types/pluginHeader.ts @@ -6,17 +6,13 @@ * @see https://github.com/metaplex-foundation/kinobi */ -import { - Serializer, - struct, - u64, - u8, -} from '@metaplex-foundation/umi/serializers'; +import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '.'; -export type PluginHeader = { version: number; pluginMapOffset: bigint }; +export type PluginHeader = { key: Key; pluginMapOffset: bigint }; export type PluginHeaderArgs = { - version: number; + key: KeyArgs; pluginMapOffset: number | bigint; }; @@ -26,7 +22,7 @@ export function getPluginHeaderSerializer(): Serializer< > { return struct( [ - ['version', u8()], + ['key', getKeySerializer()], ['pluginMapOffset', u64()], ], { description: 'PluginHeader' } diff --git a/clients/js/src/generated/types/pluginRegistry.ts b/clients/js/src/generated/types/pluginRegistry.ts index d59e7739..9d265fc8 100644 --- a/clients/js/src/generated/types/pluginRegistry.ts +++ b/clients/js/src/generated/types/pluginRegistry.ts @@ -13,18 +13,27 @@ import { tuple, } from '@metaplex-foundation/umi/serializers'; import { + Authority, + AuthorityArgs, Key, KeyArgs, RegistryData, RegistryDataArgs, + getAuthoritySerializer, getKeySerializer, getRegistryDataSerializer, } from '.'; -export type PluginRegistry = { registry: Array<[Key, RegistryData]> }; +export type PluginRegistry = { + key: Key; + registry: Array<[Key, RegistryData]>; + externalPlugins: Array<[Authority, RegistryData]>; +}; export type PluginRegistryArgs = { + key: KeyArgs; registry: Array<[KeyArgs, RegistryDataArgs]>; + externalPlugins: Array<[AuthorityArgs, RegistryDataArgs]>; }; export function getPluginRegistrySerializer(): Serializer< @@ -33,10 +42,15 @@ export function getPluginRegistrySerializer(): Serializer< > { return struct( [ + ['key', getKeySerializer()], [ 'registry', array(tuple([getKeySerializer(), getRegistryDataSerializer()])), ], + [ + 'externalPlugins', + array(tuple([getAuthoritySerializer(), getRegistryDataSerializer()])), + ], ], { description: 'PluginRegistry' } ) as Serializer; diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index e80bbb5d..8f2ab99c 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -25,6 +25,12 @@ pub enum MplAssetError { /// 4 (0x4) - Plugin not found #[error("Plugin not found")] PluginNotFound, + /// 5 (0x5) - Incorrect account + #[error("Incorrect account")] + IncorrectAccount, + /// 6 (0x6) - Incorrect asset hash + #[error("Incorrect asset hash")] + IncorrectAssetHash, } impl solana_program::program_error::PrintProgramError for MplAssetError { diff --git a/clients/rust/src/generated/instructions/transfer.rs b/clients/rust/src/generated/instructions/transfer.rs index 1efc7618..440385c5 100644 --- a/clients/rust/src/generated/instructions/transfer.rs +++ b/clients/rust/src/generated/instructions/transfer.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::CompressionProof; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -25,12 +26,16 @@ pub struct Transfer { } impl Transfer { - pub fn instruction(&self) -> solana_program::instruction::Instruction { - self.instruction_with_remaining_accounts(&[]) + pub fn instruction( + &self, + args: TransferInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) } #[allow(clippy::vec_init_then_push)] pub fn instruction_with_remaining_accounts( &self, + args: TransferInstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { let mut accounts = Vec::with_capacity(6 + remaining_accounts.len()); @@ -76,7 +81,9 @@ impl Transfer { )); } accounts.extend_from_slice(remaining_accounts); - let data = TransferInstructionData::new().try_to_vec().unwrap(); + let mut data = TransferInstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); solana_program::instruction::Instruction { program_id: crate::MPL_ASSET_ID, @@ -97,6 +104,12 @@ impl TransferInstructionData { } } +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TransferInstructionArgs { + pub compression_proof: CompressionProof, +} + /// Instruction builder. #[derive(Default)] pub struct TransferBuilder { @@ -106,6 +119,7 @@ pub struct TransferBuilder { payer: Option, new_owner: Option, log_wrapper: Option, + compression_proof: Option, __remaining_accounts: Vec, } @@ -155,6 +169,11 @@ impl TransferBuilder { self.log_wrapper = log_wrapper; self } + #[inline(always)] + pub fn compression_proof(&mut self, compression_proof: CompressionProof) -> &mut Self { + self.compression_proof = Some(compression_proof); + self + } /// Add an aditional account to the instruction. #[inline(always)] pub fn add_remaining_account( @@ -183,8 +202,14 @@ impl TransferBuilder { new_owner: self.new_owner.expect("new_owner is not set"), log_wrapper: self.log_wrapper, }; + let args = TransferInstructionArgs { + compression_proof: self + .compression_proof + .clone() + .expect("compression_proof is not set"), + }; - accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) } } @@ -220,12 +245,15 @@ pub struct TransferCpi<'a, 'b> { pub new_owner: &'b solana_program::account_info::AccountInfo<'a>, /// The SPL Noop Program pub log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// The arguments for the instruction. + pub __args: TransferInstructionArgs, } impl<'a, 'b> TransferCpi<'a, 'b> { pub fn new( program: &'b solana_program::account_info::AccountInfo<'a>, accounts: TransferCpiAccounts<'a, 'b>, + args: TransferInstructionArgs, ) -> Self { Self { __program: program, @@ -235,6 +263,7 @@ impl<'a, 'b> TransferCpi<'a, 'b> { payer: accounts.payer, new_owner: accounts.new_owner, log_wrapper: accounts.log_wrapper, + __args: args, } } #[inline(always)] @@ -322,7 +351,9 @@ impl<'a, 'b> TransferCpi<'a, 'b> { is_writable: remaining_account.2, }) }); - let data = TransferInstructionData::new().try_to_vec().unwrap(); + let mut data = TransferInstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); let instruction = solana_program::instruction::Instruction { program_id: crate::MPL_ASSET_ID, @@ -370,6 +401,7 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { payer: None, new_owner: None, log_wrapper: None, + compression_proof: None, __remaining_accounts: Vec::new(), }); Self { instruction } @@ -431,6 +463,11 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { self.instruction.log_wrapper = log_wrapper; self } + #[inline(always)] + pub fn compression_proof(&mut self, compression_proof: CompressionProof) -> &mut Self { + self.instruction.compression_proof = Some(compression_proof); + self + } /// Add an additional account to the instruction. #[inline(always)] pub fn add_remaining_account( @@ -472,6 +509,13 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { &self, signers_seeds: &[&[&[u8]]], ) -> solana_program::entrypoint::ProgramResult { + let args = TransferInstructionArgs { + compression_proof: self + .instruction + .compression_proof + .clone() + .expect("compression_proof is not set"), + }; let instruction = TransferCpi { __program: self.instruction.__program, @@ -489,6 +533,7 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { new_owner: self.instruction.new_owner.expect("new_owner is not set"), log_wrapper: self.instruction.log_wrapper, + __args: args, }; instruction.invoke_signed_with_remaining_accounts( signers_seeds, @@ -505,6 +550,7 @@ struct TransferCpiBuilderInstruction<'a, 'b> { payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, new_owner: Option<&'b solana_program::account_info::AccountInfo<'a>>, log_wrapper: Option<&'b solana_program::account_info::AccountInfo<'a>>, + compression_proof: Option, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. __remaining_accounts: Vec<( &'b solana_program::account_info::AccountInfo<'a>, diff --git a/clients/rust/src/generated/types/compression_proof.rs b/clients/rust/src/generated/types/compression_proof.rs index bd2a01ee..0ae2e256 100644 --- a/clients/rust/src/generated/types/compression_proof.rs +++ b/clients/rust/src/generated/types/compression_proof.rs @@ -5,9 +5,25 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Key; use borsh::BorshDeserialize; use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct CompressionProof {} +pub struct CompressionProof { + pub key: Key, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub update_authority: Pubkey, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub owner: Pubkey, + pub name: String, + pub uri: String, +} diff --git a/clients/rust/src/generated/types/delegate.rs b/clients/rust/src/generated/types/delegate.rs new file mode 100644 index 00000000..889bbe82 --- /dev/null +++ b/clients/rust/src/generated/types/delegate.rs @@ -0,0 +1,15 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Delegate { + pub frozen: bool, +} diff --git a/clients/rust/src/generated/types/key.rs b/clients/rust/src/generated/types/key.rs index e6f27e58..1da6dc86 100644 --- a/clients/rust/src/generated/types/key.rs +++ b/clients/rust/src/generated/types/key.rs @@ -16,4 +16,6 @@ pub enum Key { HashedAsset, Collection, HashedCollection, + PluginHeader, + PluginRegistry, } diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index b24b2c4e..f5487ca3 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod collection; pub(crate) mod compression_proof; pub(crate) mod creator; pub(crate) mod data_state; +pub(crate) mod delegate; pub(crate) mod extra_accounts; pub(crate) mod key; pub(crate) mod migration_level; @@ -24,6 +25,7 @@ pub use self::collection::*; pub use self::compression_proof::*; pub use self::creator::*; pub use self::data_state::*; +pub use self::delegate::*; pub use self::extra_accounts::*; pub use self::key::*; pub use self::migration_level::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index e0a2fff5..3f54bda4 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -5,16 +5,17 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Delegate; use borsh::BorshDeserialize; use borsh::BorshSerialize; -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Plugin { Reserved, Royalties, MasterEdition, PrintEdition, - Delegate, + Delegate(Delegate), Inscription, } diff --git a/clients/rust/src/generated/types/plugin_header.rs b/clients/rust/src/generated/types/plugin_header.rs index cd7b8e00..083a35b5 100644 --- a/clients/rust/src/generated/types/plugin_header.rs +++ b/clients/rust/src/generated/types/plugin_header.rs @@ -5,12 +5,13 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Key; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PluginHeader { - pub version: u8, + pub key: Key, pub plugin_map_offset: u64, } diff --git a/clients/rust/src/generated/types/plugin_registry.rs b/clients/rust/src/generated/types/plugin_registry.rs index 60661820..e9966856 100644 --- a/clients/rust/src/generated/types/plugin_registry.rs +++ b/clients/rust/src/generated/types/plugin_registry.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Authority; use crate::generated::types::Key; use crate::generated::types::RegistryData; use borsh::BorshDeserialize; @@ -13,5 +14,7 @@ use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PluginRegistry { + pub key: Key, pub registry: Vec<(Key, RegistryData)>, + pub external_plugins: Vec<(Authority, RegistryData)>, } diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index 94919eb6..a914c24f 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -779,6 +779,12 @@ "type": { "kind": "struct", "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, { "name": "registry", "type": { @@ -793,6 +799,21 @@ ] } } + }, + { + "name": "externalPlugins", + "type": { + "vec": { + "tuple": [ + { + "defined": "Authority" + }, + { + "defined": "RegistryData" + } + ] + } + } } ] } @@ -813,6 +834,18 @@ ] } }, + { + "name": "Delegate", + "type": { + "kind": "struct", + "fields": [ + { + "name": "frozen", + "type": "bool" + } + ] + } + }, { "name": "Creator", "type": { @@ -853,13 +886,6 @@ ] } }, - { - "name": "CompressionProof", - "type": { - "kind": "struct", - "fields": [] - } - }, { "name": "BurnArgs", "type": { @@ -948,7 +974,14 @@ "name": "TransferArgs", "type": { "kind": "struct", - "fields": [] + "fields": [ + { + "name": "compressionProof", + "type": { + "defined": "CompressionProof" + } + } + ] } }, { @@ -967,14 +1000,46 @@ ] } }, + { + "name": "CompressionProof", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "updateAuthority", + "type": "publicKey" + }, + { + "name": "owner", + "type": "publicKey" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "uri", + "type": "string" + } + ] + } + }, { "name": "PluginHeader", "type": { "kind": "struct", "fields": [ { - "name": "version", - "type": "u8" + "name": "key", + "type": { + "defined": "Key" + } }, { "name": "pluginMapOffset", @@ -1001,7 +1066,12 @@ "name": "PrintEdition" }, { - "name": "Delegate" + "name": "Delegate", + "fields": [ + { + "defined": "Delegate" + } + ] }, { "name": "Inscription" @@ -1119,6 +1189,12 @@ }, { "name": "HashedCollection" + }, + { + "name": "PluginHeader" + }, + { + "name": "PluginRegistry" } ] } @@ -1163,6 +1239,16 @@ "code": 4, "name": "PluginNotFound", "msg": "Plugin not found" + }, + { + "code": 5, + "name": "IncorrectAccount", + "msg": "Incorrect account" + }, + { + "code": 6, + "name": "IncorrectAssetHash", + "msg": "Incorrect asset hash" } ], "metadata": { From 75cb76e2f486bb60775d1fff51a62d29f0768305 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 18:59:56 -0700 Subject: [PATCH 12/20] Adding basic delegate. --- programs/mpl-asset/src/error.rs | 4 + programs/mpl-asset/src/plugins/delegate.rs | 23 ++++ programs/mpl-asset/src/plugins/utils.rs | 104 ++++++++++++++++-- programs/mpl-asset/src/state/mod.rs | 2 + programs/mpl-asset/src/state/plugin_header.rs | 4 +- 5 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 programs/mpl-asset/src/plugins/delegate.rs diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index b0e48b35..dd0ceaf8 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -27,6 +27,10 @@ pub enum MplAssetError { /// 4 - Plugin not found #[error("Plugin not found")] PluginNotFound, + + /// 5 - Numerical Overflow + #[error("Numerical Overflow")] + NumericalOverflow, } impl PrintProgramError for MplAssetError { diff --git a/programs/mpl-asset/src/plugins/delegate.rs b/programs/mpl-asset/src/plugins/delegate.rs new file mode 100644 index 00000000..df980666 --- /dev/null +++ b/programs/mpl-asset/src/plugins/delegate.rs @@ -0,0 +1,23 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::{state::Key, utils::DataBlob}; + +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct Delegate { + key: Key, // 1 + frozen: bool, // 1 +} + +impl DataBlob for Delegate { + fn get_initial_size() -> usize { + 2 + } + + fn get_size(&self) -> usize { + 2 + } + + fn key() -> crate::state::Key { + Key::Delegate + } +} diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 111e72f1..f3068280 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -1,4 +1,4 @@ -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::resize_or_reallocate_account_raw; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, @@ -10,7 +10,7 @@ use crate::{ utils::DataBlob, }; -use super::{Plugin, PluginRegistry}; +use super::{Plugin, PluginRegistry, RegistryData}; /// Create plugin header and registry if it doesn't exist pub fn create_idempotent<'a>( @@ -27,8 +27,8 @@ pub fn create_idempotent<'a>( if asset.get_size() == account.data_len() { // They don't exist, so create them. let header = PluginHeader { - version: 1, - plugin_map_offset: asset.get_size() + PluginHeader::get_initial_size(), + key: Key::PluginHeader, + plugin_registry_offset: asset.get_size() + PluginHeader::get_initial_size(), }; let registry = PluginRegistry { registry: vec![] }; @@ -36,11 +36,11 @@ pub fn create_idempotent<'a>( account, payer, system_program, - header.plugin_map_offset + PluginRegistry::get_initial_size(), + header.plugin_registry_offset + PluginRegistry::get_initial_size(), )?; header.save(account, asset.get_size())?; - registry.save(account, header.plugin_map_offset)?; + registry.save(account, header.plugin_registry_offset)?; } Ok(()) @@ -66,7 +66,8 @@ pub fn fetch_plugin( let asset = Asset::deserialize(&mut bytes)?; let header = PluginHeader::load(account, asset.get_size())?; - let PluginRegistry { registry } = PluginRegistry::load(account, header.plugin_map_offset)?; + let PluginRegistry { registry, .. } = + PluginRegistry::load(account, header.plugin_registry_offset)?; // Find the plugin in the registry. let plugin_data = registry @@ -88,7 +89,94 @@ pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { let asset = Asset::deserialize(&mut bytes)?; let header = PluginHeader::load(account, asset.get_size())?; - let PluginRegistry { registry } = PluginRegistry::load(account, header.plugin_map_offset)?; + let PluginRegistry { registry, .. } = + PluginRegistry::load(account, header.plugin_registry_offset)?; Ok(registry.iter().map(|(key, _)| *key).collect()) } + +/// Add a plugin into the registry +pub fn add_plugin<'a>( + plugin: &Plugin, + authority: Authority, + account: &AccountInfo<'a>, + payer: &AccountInfo<'a>, + system_program: &AccountInfo<'a>, +) -> ProgramResult { + let asset = { + let mut bytes: &[u8] = &(*account.data).borrow(); + Asset::deserialize(&mut bytes)? + }; + + //TODO: Bytemuck this. + let mut header = PluginHeader::load(account, asset.get_size())?; + let mut plugin_registry = PluginRegistry::load(account, header.plugin_registry_offset)?; + + let (plugin_size, key) = match plugin { + Plugin::Reserved => todo!(), + Plugin::Royalties => todo!(), + Plugin::MasterEdition => todo!(), + Plugin::PrintEdition => todo!(), + Plugin::Delegate(delegate) => (delegate.get_size(), Key::Delegate), + Plugin::Inscription => todo!(), + }; + + if let Some((_, registry_data)) = plugin_registry + .registry + .iter_mut() + .find(|(search_key, registry_data)| search_key == &key) + { + registry_data.authorities.push(authority); + + let authority_bytes = authority.try_to_vec()?; + + let new_size = account + .data_len() + .checked_add(authority_bytes.len()) + .ok_or(MplAssetError::NumericalOverflow)?; + resize_or_reallocate_account_raw(account, payer, system_program, new_size)?; + + plugin_registry.save(account, header.plugin_registry_offset); + } else { + let authority_bytes = authority.try_to_vec()?; + + let new_size = account + .data_len() + .checked_add(plugin_size) + .ok_or(MplAssetError::NumericalOverflow)? + .checked_add(authority_bytes.len()) + .ok_or(MplAssetError::NumericalOverflow)?; + + let old_registry_offset = header.plugin_registry_offset; + let new_registry_offset = header + .plugin_registry_offset + .checked_add(plugin_size) + .ok_or(MplAssetError::NumericalOverflow)? + .checked_add(authority_bytes.len()) + .ok_or(MplAssetError::NumericalOverflow)?; + + header.plugin_registry_offset = new_registry_offset; + plugin_registry.registry.push(( + key, + RegistryData { + offset: old_registry_offset, + authorities: vec![authority], + }, + )); + + resize_or_reallocate_account_raw(account, payer, system_program, new_size); + + header.save(account, asset.get_size()); + match plugin { + Plugin::Reserved => todo!(), + Plugin::Royalties => todo!(), + Plugin::MasterEdition => todo!(), + Plugin::PrintEdition => todo!(), + Plugin::Delegate(delegate) => delegate.save(account, old_registry_offset), + Plugin::Inscription => todo!(), + }; + plugin_registry.save(account, new_registry_offset); + } + + Ok(()) +} diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 87d8ae55..6a0472cf 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -53,6 +53,8 @@ pub enum Key { Collection, HashedCollection, PluginHeader, + PluginRegistry, + Delegate, } #[repr(C)] diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index efe97dbb..4adb4f17 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -9,8 +9,8 @@ use crate::{error::MplAssetError, state::Key, utils::DataBlob}; #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct PluginHeader { - pub version: u8, // 1 - pub plugin_map_offset: usize, // 8 + pub key: Key, + pub plugin_registry_offset: usize, // 8 } impl DataBlob for PluginHeader { From 1059b091bef24e17856a2540e7fb52a4ee5fabec Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sat, 17 Feb 2024 20:12:44 -0700 Subject: [PATCH 13/20] Working basic delegate. --- clients/js/src/generated/accounts/index.ts | 2 + .../js/src/generated/accounts/pluginHeader.ts | 127 +++++++++++++++ .../src/generated/accounts/pluginRegistry.ts | 152 ++++++++++++++++++ clients/js/src/generated/errors/mplAsset.ts | 21 ++- clients/js/src/generated/types/authority.ts | 12 ++ clients/js/src/generated/types/delegate.ts | 15 +- .../generated/types/externalPluginRecord.ts | 37 +++++ clients/js/src/generated/types/index.ts | 4 +- clients/js/src/generated/types/key.ts | 1 + .../js/src/generated/types/pluginHeader.ts | 30 ---- .../js/src/generated/types/pluginRegistry.ts | 57 ------- .../js/src/generated/types/registryRecord.ts | 34 ++++ clients/js/test/delegate.test.ts | 10 +- clients/rust/src/generated/accounts/mod.rs | 4 + .../src/generated/accounts/plugin_header.rs | 38 +++++ .../src/generated/accounts/plugin_registry.rs | 39 +++++ .../rust/src/generated/errors/mpl_asset.rs | 7 +- clients/rust/src/generated/types/authority.rs | 7 + clients/rust/src/generated/types/delegate.rs | 2 + ..._registry.rs => external_plugin_record.rs} | 8 +- clients/rust/src/generated/types/key.rs | 1 + clients/rust/src/generated/types/mod.rs | 8 +- .../{plugin_header.rs => registry_record.rs} | 5 +- idls/mpl_asset_program.json | 133 ++++++++++----- programs/mpl-asset/src/plugins/delegate.rs | 21 ++- programs/mpl-asset/src/plugins/mod.rs | 28 +++- programs/mpl-asset/src/plugins/utils.rs | 83 ++++++---- programs/mpl-asset/src/processor/delegate.rs | 36 +++-- programs/mpl-asset/src/state/mod.rs | 7 + programs/mpl-asset/src/state/plugin_header.rs | 3 +- 30 files changed, 725 insertions(+), 207 deletions(-) create mode 100644 clients/js/src/generated/accounts/pluginHeader.ts create mode 100644 clients/js/src/generated/accounts/pluginRegistry.ts create mode 100644 clients/js/src/generated/types/externalPluginRecord.ts delete mode 100644 clients/js/src/generated/types/pluginHeader.ts delete mode 100644 clients/js/src/generated/types/pluginRegistry.ts create mode 100644 clients/js/src/generated/types/registryRecord.ts create mode 100644 clients/rust/src/generated/accounts/plugin_header.rs create mode 100644 clients/rust/src/generated/accounts/plugin_registry.rs rename clients/rust/src/generated/types/{plugin_registry.rs => external_plugin_record.rs} (74%) rename clients/rust/src/generated/types/{plugin_header.rs => registry_record.rs} (83%) diff --git a/clients/js/src/generated/accounts/index.ts b/clients/js/src/generated/accounts/index.ts index a9c118f1..2419a1f2 100644 --- a/clients/js/src/generated/accounts/index.ts +++ b/clients/js/src/generated/accounts/index.ts @@ -8,3 +8,5 @@ export * from './asset'; export * from './hashedAsset'; +export * from './pluginHeader'; +export * from './pluginRegistry'; diff --git a/clients/js/src/generated/accounts/pluginHeader.ts b/clients/js/src/generated/accounts/pluginHeader.ts new file mode 100644 index 00000000..c5d96a0e --- /dev/null +++ b/clients/js/src/generated/accounts/pluginHeader.ts @@ -0,0 +1,127 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Account, + Context, + Pda, + PublicKey, + RpcAccount, + RpcGetAccountOptions, + RpcGetAccountsOptions, + assertAccountExists, + deserializeAccount, + gpaBuilder, + publicKey as toPublicKey, +} from '@metaplex-foundation/umi'; +import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '../types'; + +export type PluginHeader = Account; + +export type PluginHeaderAccountData = { + key: Key; + pluginRegistryOffset: bigint; +}; + +export type PluginHeaderAccountDataArgs = { + key: KeyArgs; + pluginRegistryOffset: number | bigint; +}; + +export function getPluginHeaderAccountDataSerializer(): Serializer< + PluginHeaderAccountDataArgs, + PluginHeaderAccountData +> { + return struct( + [ + ['key', getKeySerializer()], + ['pluginRegistryOffset', u64()], + ], + { description: 'PluginHeaderAccountData' } + ) as Serializer; +} + +export function deserializePluginHeader(rawAccount: RpcAccount): PluginHeader { + return deserializeAccount(rawAccount, getPluginHeaderAccountDataSerializer()); +} + +export async function fetchPluginHeader( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + assertAccountExists(maybeAccount, 'PluginHeader'); + return deserializePluginHeader(maybeAccount); +} + +export async function safeFetchPluginHeader( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + return maybeAccount.exists ? deserializePluginHeader(maybeAccount) : null; +} + +export async function fetchAllPluginHeader( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts.map((maybeAccount) => { + assertAccountExists(maybeAccount, 'PluginHeader'); + return deserializePluginHeader(maybeAccount); + }); +} + +export async function safeFetchAllPluginHeader( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts + .filter((maybeAccount) => maybeAccount.exists) + .map((maybeAccount) => deserializePluginHeader(maybeAccount as RpcAccount)); +} + +export function getPluginHeaderGpaBuilder( + context: Pick +) { + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + return gpaBuilder(context, programId) + .registerFields<{ key: KeyArgs; pluginRegistryOffset: number | bigint }>({ + key: [0, getKeySerializer()], + pluginRegistryOffset: [1, u64()], + }) + .deserializeUsing((account) => + deserializePluginHeader(account) + ); +} + +export function getPluginHeaderSize(): number { + return 9; +} diff --git a/clients/js/src/generated/accounts/pluginRegistry.ts b/clients/js/src/generated/accounts/pluginRegistry.ts new file mode 100644 index 00000000..aaec2e54 --- /dev/null +++ b/clients/js/src/generated/accounts/pluginRegistry.ts @@ -0,0 +1,152 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { + Account, + Context, + Pda, + PublicKey, + RpcAccount, + RpcGetAccountOptions, + RpcGetAccountsOptions, + assertAccountExists, + deserializeAccount, + gpaBuilder, + publicKey as toPublicKey, +} from '@metaplex-foundation/umi'; +import { + Serializer, + array, + struct, +} from '@metaplex-foundation/umi/serializers'; +import { + ExternalPluginRecord, + ExternalPluginRecordArgs, + Key, + KeyArgs, + RegistryRecord, + RegistryRecordArgs, + getExternalPluginRecordSerializer, + getKeySerializer, + getRegistryRecordSerializer, +} from '../types'; + +export type PluginRegistry = Account; + +export type PluginRegistryAccountData = { + key: Key; + registry: Array; + externalPlugins: Array; +}; + +export type PluginRegistryAccountDataArgs = { + key: KeyArgs; + registry: Array; + externalPlugins: Array; +}; + +export function getPluginRegistryAccountDataSerializer(): Serializer< + PluginRegistryAccountDataArgs, + PluginRegistryAccountData +> { + return struct( + [ + ['key', getKeySerializer()], + ['registry', array(getRegistryRecordSerializer())], + ['externalPlugins', array(getExternalPluginRecordSerializer())], + ], + { description: 'PluginRegistryAccountData' } + ) as Serializer; +} + +export function deserializePluginRegistry( + rawAccount: RpcAccount +): PluginRegistry { + return deserializeAccount( + rawAccount, + getPluginRegistryAccountDataSerializer() + ); +} + +export async function fetchPluginRegistry( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + assertAccountExists(maybeAccount, 'PluginRegistry'); + return deserializePluginRegistry(maybeAccount); +} + +export async function safeFetchPluginRegistry( + context: Pick, + publicKey: PublicKey | Pda, + options?: RpcGetAccountOptions +): Promise { + const maybeAccount = await context.rpc.getAccount( + toPublicKey(publicKey, false), + options + ); + return maybeAccount.exists ? deserializePluginRegistry(maybeAccount) : null; +} + +export async function fetchAllPluginRegistry( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts.map((maybeAccount) => { + assertAccountExists(maybeAccount, 'PluginRegistry'); + return deserializePluginRegistry(maybeAccount); + }); +} + +export async function safeFetchAllPluginRegistry( + context: Pick, + publicKeys: Array, + options?: RpcGetAccountsOptions +): Promise { + const maybeAccounts = await context.rpc.getAccounts( + publicKeys.map((key) => toPublicKey(key, false)), + options + ); + return maybeAccounts + .filter((maybeAccount) => maybeAccount.exists) + .map((maybeAccount) => + deserializePluginRegistry(maybeAccount as RpcAccount) + ); +} + +export function getPluginRegistryGpaBuilder( + context: Pick +) { + const programId = context.programs.getPublicKey( + 'mplAsset', + 'ASSETp3DinZKfiAyvdQG16YWWLJ2X3ZKjg9zku7n1sZD' + ); + return gpaBuilder(context, programId) + .registerFields<{ + key: KeyArgs; + registry: Array; + externalPlugins: Array; + }>({ + key: [0, getKeySerializer()], + registry: [1, array(getRegistryRecordSerializer())], + externalPlugins: [null, array(getExternalPluginRecordSerializer())], + }) + .deserializeUsing((account) => + deserializePluginRegistry(account) + ); +} diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index 71538e57..36cc5d75 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -80,30 +80,43 @@ export class PluginNotFoundError extends ProgramError { codeToErrorMap.set(0x4, PluginNotFoundError); nameToErrorMap.set('PluginNotFound', PluginNotFoundError); +/** NumericalOverflow: Numerical Overflow */ +export class NumericalOverflowError extends ProgramError { + readonly name: string = 'NumericalOverflow'; + + readonly code: number = 0x5; // 5 + + constructor(program: Program, cause?: Error) { + super('Numerical Overflow', program, cause); + } +} +codeToErrorMap.set(0x5, NumericalOverflowError); +nameToErrorMap.set('NumericalOverflow', NumericalOverflowError); + /** IncorrectAccount: Incorrect account */ export class IncorrectAccountError extends ProgramError { readonly name: string = 'IncorrectAccount'; - readonly code: number = 0x5; // 5 + readonly code: number = 0x6; // 6 constructor(program: Program, cause?: Error) { super('Incorrect account', program, cause); } } -codeToErrorMap.set(0x5, IncorrectAccountError); +codeToErrorMap.set(0x6, IncorrectAccountError); nameToErrorMap.set('IncorrectAccount', IncorrectAccountError); /** IncorrectAssetHash: Incorrect asset hash */ export class IncorrectAssetHashError extends ProgramError { readonly name: string = 'IncorrectAssetHash'; - readonly code: number = 0x6; // 6 + readonly code: number = 0x7; // 7 constructor(program: Program, cause?: Error) { super('Incorrect asset hash', program, cause); } } -codeToErrorMap.set(0x6, IncorrectAssetHashError); +codeToErrorMap.set(0x7, IncorrectAssetHashError); nameToErrorMap.set('IncorrectAssetHash', IncorrectAssetHashError); /** diff --git a/clients/js/src/generated/types/authority.ts b/clients/js/src/generated/types/authority.ts index 427fbdc3..e33ad202 100644 --- a/clients/js/src/generated/types/authority.ts +++ b/clients/js/src/generated/types/authority.ts @@ -20,12 +20,14 @@ import { Plugin, PluginArgs, getPluginSerializer } from '.'; export type Authority = | { __kind: 'Owner' } + | { __kind: 'Pubkey'; address: PublicKey } | { __kind: 'Permanent'; address: PublicKey } | { __kind: 'SameAs'; plugin: Plugin } | { __kind: 'Collection' }; export type AuthorityArgs = | { __kind: 'Owner' } + | { __kind: 'Pubkey'; address: PublicKey } | { __kind: 'Permanent'; address: PublicKey } | { __kind: 'SameAs'; plugin: PluginArgs } | { __kind: 'Collection' }; @@ -34,6 +36,12 @@ export function getAuthoritySerializer(): Serializer { return dataEnum( [ ['Owner', unit()], + [ + 'Pubkey', + struct>([ + ['address', publicKeySerializer()], + ]), + ], [ 'Permanent', struct>([ @@ -56,6 +64,10 @@ export function getAuthoritySerializer(): Serializer { export function authority( kind: 'Owner' ): GetDataEnumKind; +export function authority( + kind: 'Pubkey', + data: GetDataEnumKindContent +): GetDataEnumKind; export function authority( kind: 'Permanent', data: GetDataEnumKindContent diff --git a/clients/js/src/generated/types/delegate.ts b/clients/js/src/generated/types/delegate.ts index d775b8ce..55ad2c58 100644 --- a/clients/js/src/generated/types/delegate.ts +++ b/clients/js/src/generated/types/delegate.ts @@ -7,13 +7,18 @@ */ import { Serializer, bool, struct } from '@metaplex-foundation/umi/serializers'; +import { Key, KeyArgs, getKeySerializer } from '.'; -export type Delegate = { frozen: boolean }; +export type Delegate = { key: Key; frozen: boolean }; -export type DelegateArgs = Delegate; +export type DelegateArgs = { key: KeyArgs; frozen: boolean }; export function getDelegateSerializer(): Serializer { - return struct([['frozen', bool()]], { - description: 'Delegate', - }) as Serializer; + return struct( + [ + ['key', getKeySerializer()], + ['frozen', bool()], + ], + { description: 'Delegate' } + ) as Serializer; } diff --git a/clients/js/src/generated/types/externalPluginRecord.ts b/clients/js/src/generated/types/externalPluginRecord.ts new file mode 100644 index 00000000..acb1f9b3 --- /dev/null +++ b/clients/js/src/generated/types/externalPluginRecord.ts @@ -0,0 +1,37 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; +import { + Authority, + AuthorityArgs, + RegistryData, + RegistryDataArgs, + getAuthoritySerializer, + getRegistryDataSerializer, +} from '.'; + +export type ExternalPluginRecord = { authority: Authority; data: RegistryData }; + +export type ExternalPluginRecordArgs = { + authority: AuthorityArgs; + data: RegistryDataArgs; +}; + +export function getExternalPluginRecordSerializer(): Serializer< + ExternalPluginRecordArgs, + ExternalPluginRecord +> { + return struct( + [ + ['authority', getAuthoritySerializer()], + ['data', getRegistryDataSerializer()], + ], + { description: 'ExternalPluginRecord' } + ) as Serializer; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index b5c2dccd..1695fdb1 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -12,11 +12,11 @@ export * from './compressionProof'; export * from './creator'; export * from './dataState'; export * from './delegate'; +export * from './externalPluginRecord'; export * from './extraAccounts'; export * from './key'; export * from './migrationLevel'; export * from './plugin'; -export * from './pluginHeader'; -export * from './pluginRegistry'; export * from './registryData'; +export * from './registryRecord'; export * from './royalties'; diff --git a/clients/js/src/generated/types/key.ts b/clients/js/src/generated/types/key.ts index 169a0952..8558a0b3 100644 --- a/clients/js/src/generated/types/key.ts +++ b/clients/js/src/generated/types/key.ts @@ -16,6 +16,7 @@ export enum Key { HashedCollection, PluginHeader, PluginRegistry, + Delegate, } export type KeyArgs = Key; diff --git a/clients/js/src/generated/types/pluginHeader.ts b/clients/js/src/generated/types/pluginHeader.ts deleted file mode 100644 index 696a2ee9..00000000 --- a/clients/js/src/generated/types/pluginHeader.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers'; -import { Key, KeyArgs, getKeySerializer } from '.'; - -export type PluginHeader = { key: Key; pluginMapOffset: bigint }; - -export type PluginHeaderArgs = { - key: KeyArgs; - pluginMapOffset: number | bigint; -}; - -export function getPluginHeaderSerializer(): Serializer< - PluginHeaderArgs, - PluginHeader -> { - return struct( - [ - ['key', getKeySerializer()], - ['pluginMapOffset', u64()], - ], - { description: 'PluginHeader' } - ) as Serializer; -} diff --git a/clients/js/src/generated/types/pluginRegistry.ts b/clients/js/src/generated/types/pluginRegistry.ts deleted file mode 100644 index 9d265fc8..00000000 --- a/clients/js/src/generated/types/pluginRegistry.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/metaplex-foundation/kinobi - */ - -import { - Serializer, - array, - struct, - tuple, -} from '@metaplex-foundation/umi/serializers'; -import { - Authority, - AuthorityArgs, - Key, - KeyArgs, - RegistryData, - RegistryDataArgs, - getAuthoritySerializer, - getKeySerializer, - getRegistryDataSerializer, -} from '.'; - -export type PluginRegistry = { - key: Key; - registry: Array<[Key, RegistryData]>; - externalPlugins: Array<[Authority, RegistryData]>; -}; - -export type PluginRegistryArgs = { - key: KeyArgs; - registry: Array<[KeyArgs, RegistryDataArgs]>; - externalPlugins: Array<[AuthorityArgs, RegistryDataArgs]>; -}; - -export function getPluginRegistrySerializer(): Serializer< - PluginRegistryArgs, - PluginRegistry -> { - return struct( - [ - ['key', getKeySerializer()], - [ - 'registry', - array(tuple([getKeySerializer(), getRegistryDataSerializer()])), - ], - [ - 'externalPlugins', - array(tuple([getAuthoritySerializer(), getRegistryDataSerializer()])), - ], - ], - { description: 'PluginRegistry' } - ) as Serializer; -} diff --git a/clients/js/src/generated/types/registryRecord.ts b/clients/js/src/generated/types/registryRecord.ts new file mode 100644 index 00000000..2680179a --- /dev/null +++ b/clients/js/src/generated/types/registryRecord.ts @@ -0,0 +1,34 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; +import { + Key, + KeyArgs, + RegistryData, + RegistryDataArgs, + getKeySerializer, + getRegistryDataSerializer, +} from '.'; + +export type RegistryRecord = { key: Key; data: RegistryData }; + +export type RegistryRecordArgs = { key: KeyArgs; data: RegistryDataArgs }; + +export function getRegistryRecordSerializer(): Serializer< + RegistryRecordArgs, + RegistryRecord +> { + return struct( + [ + ['key', getKeySerializer()], + ['data', getRegistryDataSerializer()], + ], + { description: 'RegistryRecord' } + ) as Serializer; +} diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/delegate.test.ts index 82d4af53..b8ac7614 100644 --- a/clients/js/test/delegate.test.ts +++ b/clients/js/test/delegate.test.ts @@ -1,7 +1,7 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; // import { base58 } from '@metaplex-foundation/umi/serializers'; -import { DataState, create, delegate, fetchAsset, getAssetAccountDataSerializer, getPluginHeaderSerializer, getPluginRegistrySerializer, } from '../src'; +import { DataState, create, delegate, fetchAsset, getAssetAccountDataSerializer, getDelegateSerializer, getPluginHeaderAccountDataSerializer, getPluginRegistryAccountDataSerializer, } from '../src'; import { createUmi } from './_setup'; test('it initializes the plugin system correctly', async (t) => { @@ -31,10 +31,12 @@ test('it initializes the plugin system correctly', async (t) => { const pluginData = await umi.rpc.getAccount(assetAddress.publicKey); if (pluginData.exists) { - const pluginHeader = getPluginHeaderSerializer().deserialize(pluginData.data, assetData.length); + const pluginHeader = getPluginHeaderAccountDataSerializer().deserialize(pluginData.data, assetData.length)[0]; console.log("After Plugins:\n", pluginHeader); - const pluginRegistry = getPluginRegistrySerializer().deserialize(pluginData.data, Number(pluginHeader[0].pluginMapOffset)); - console.log(pluginRegistry); + const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(pluginData.data, Number(pluginHeader.pluginRegistryOffset)); + console.log(JSON.stringify(pluginRegistry, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + const delegatePlugin = getDelegateSerializer().deserialize(pluginData.data, Number(pluginRegistry[0].registry[0].data.offset)); + console.log(delegatePlugin); } else { t.fail("Plugin data not found"); } diff --git a/clients/rust/src/generated/accounts/mod.rs b/clients/rust/src/generated/accounts/mod.rs index 0e5a968c..6f60f76e 100644 --- a/clients/rust/src/generated/accounts/mod.rs +++ b/clients/rust/src/generated/accounts/mod.rs @@ -7,6 +7,10 @@ pub(crate) mod asset; pub(crate) mod hashed_asset; +pub(crate) mod plugin_header; +pub(crate) mod plugin_registry; pub use self::asset::*; pub use self::hashed_asset::*; +pub use self::plugin_header::*; +pub use self::plugin_registry::*; diff --git a/clients/rust/src/generated/accounts/plugin_header.rs b/clients/rust/src/generated/accounts/plugin_header.rs new file mode 100644 index 00000000..f8bdd5aa --- /dev/null +++ b/clients/rust/src/generated/accounts/plugin_header.rs @@ -0,0 +1,38 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::Key; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PluginHeader { + pub key: Key, + pub plugin_registry_offset: u64, +} + +impl PluginHeader { + pub const LEN: usize = 9; + + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for PluginHeader { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} diff --git a/clients/rust/src/generated/accounts/plugin_registry.rs b/clients/rust/src/generated/accounts/plugin_registry.rs new file mode 100644 index 00000000..3f28b1ed --- /dev/null +++ b/clients/rust/src/generated/accounts/plugin_registry.rs @@ -0,0 +1,39 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use crate::generated::types::ExternalPluginRecord; +use crate::generated::types::Key; +use crate::generated::types::RegistryRecord; +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PluginRegistry { + pub key: Key, + pub registry: Vec, + pub external_plugins: Vec, +} + +impl PluginRegistry { + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for PluginRegistry { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index 8f2ab99c..3dbe990d 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -25,10 +25,13 @@ pub enum MplAssetError { /// 4 (0x4) - Plugin not found #[error("Plugin not found")] PluginNotFound, - /// 5 (0x5) - Incorrect account + /// 5 (0x5) - Numerical Overflow + #[error("Numerical Overflow")] + NumericalOverflow, + /// 6 (0x6) - Incorrect account #[error("Incorrect account")] IncorrectAccount, - /// 6 (0x6) - Incorrect asset hash + /// 7 (0x7) - Incorrect asset hash #[error("Incorrect asset hash")] IncorrectAssetHash, } diff --git a/clients/rust/src/generated/types/authority.rs b/clients/rust/src/generated/types/authority.rs index 42db0ebc..f8ae9ba3 100644 --- a/clients/rust/src/generated/types/authority.rs +++ b/clients/rust/src/generated/types/authority.rs @@ -14,6 +14,13 @@ use solana_program::pubkey::Pubkey; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Authority { Owner, + Pubkey { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + address: Pubkey, + }, Permanent { #[cfg_attr( feature = "serde", diff --git a/clients/rust/src/generated/types/delegate.rs b/clients/rust/src/generated/types/delegate.rs index 889bbe82..8a51bd3d 100644 --- a/clients/rust/src/generated/types/delegate.rs +++ b/clients/rust/src/generated/types/delegate.rs @@ -5,11 +5,13 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::Key; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Delegate { + pub key: Key, pub frozen: bool, } diff --git a/clients/rust/src/generated/types/plugin_registry.rs b/clients/rust/src/generated/types/external_plugin_record.rs similarity index 74% rename from clients/rust/src/generated/types/plugin_registry.rs rename to clients/rust/src/generated/types/external_plugin_record.rs index e9966856..cfad2374 100644 --- a/clients/rust/src/generated/types/plugin_registry.rs +++ b/clients/rust/src/generated/types/external_plugin_record.rs @@ -6,15 +6,13 @@ //! use crate::generated::types::Authority; -use crate::generated::types::Key; use crate::generated::types::RegistryData; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PluginRegistry { - pub key: Key, - pub registry: Vec<(Key, RegistryData)>, - pub external_plugins: Vec<(Authority, RegistryData)>, +pub struct ExternalPluginRecord { + pub authority: Authority, + pub data: RegistryData, } diff --git a/clients/rust/src/generated/types/key.rs b/clients/rust/src/generated/types/key.rs index 1da6dc86..956e7fea 100644 --- a/clients/rust/src/generated/types/key.rs +++ b/clients/rust/src/generated/types/key.rs @@ -18,4 +18,5 @@ pub enum Key { HashedCollection, PluginHeader, PluginRegistry, + Delegate, } diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index f5487ca3..8481e0fc 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -11,13 +11,13 @@ pub(crate) mod compression_proof; pub(crate) mod creator; pub(crate) mod data_state; pub(crate) mod delegate; +pub(crate) mod external_plugin_record; pub(crate) mod extra_accounts; pub(crate) mod key; pub(crate) mod migration_level; pub(crate) mod plugin; -pub(crate) mod plugin_header; -pub(crate) mod plugin_registry; pub(crate) mod registry_data; +pub(crate) mod registry_record; pub(crate) mod royalties; pub use self::authority::*; @@ -26,11 +26,11 @@ pub use self::compression_proof::*; pub use self::creator::*; pub use self::data_state::*; pub use self::delegate::*; +pub use self::external_plugin_record::*; pub use self::extra_accounts::*; pub use self::key::*; pub use self::migration_level::*; pub use self::plugin::*; -pub use self::plugin_header::*; -pub use self::plugin_registry::*; pub use self::registry_data::*; +pub use self::registry_record::*; pub use self::royalties::*; diff --git a/clients/rust/src/generated/types/plugin_header.rs b/clients/rust/src/generated/types/registry_record.rs similarity index 83% rename from clients/rust/src/generated/types/plugin_header.rs rename to clients/rust/src/generated/types/registry_record.rs index 083a35b5..741601f8 100644 --- a/clients/rust/src/generated/types/plugin_header.rs +++ b/clients/rust/src/generated/types/registry_record.rs @@ -6,12 +6,13 @@ //! use crate::generated::types::Key; +use crate::generated::types::RegistryData; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PluginHeader { +pub struct RegistryRecord { pub key: Key, - pub plugin_map_offset: u64, + pub data: RegistryData, } diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index a914c24f..c5f55b0d 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -699,6 +699,36 @@ } ], "accounts": [ + { + "name": "PluginRegistry", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "registry", + "type": { + "vec": { + "defined": "RegistryRecord" + } + } + }, + { + "name": "externalPlugins", + "type": { + "vec": { + "defined": "ExternalPluginRecord" + } + } + } + ] + } + }, { "name": "Asset", "type": { @@ -751,6 +781,24 @@ } ] } + }, + { + "name": "PluginHeader", + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, + { + "name": "pluginRegistryOffset", + "type": "u64" + } + ] + } } ], "types": [ @@ -775,7 +823,7 @@ } }, { - "name": "PluginRegistry", + "name": "RegistryRecord", "type": { "kind": "struct", "fields": [ @@ -786,33 +834,29 @@ } }, { - "name": "registry", + "name": "data", "type": { - "vec": { - "tuple": [ - { - "defined": "Key" - }, - { - "defined": "RegistryData" - } - ] - } + "defined": "RegistryData" + } + } + ] + } + }, + { + "name": "ExternalPluginRecord", + "type": { + "kind": "struct", + "fields": [ + { + "name": "authority", + "type": { + "defined": "Authority" } }, { - "name": "externalPlugins", + "name": "data", "type": { - "vec": { - "tuple": [ - { - "defined": "Authority" - }, - { - "defined": "RegistryData" - } - ] - } + "defined": "RegistryData" } } ] @@ -839,6 +883,12 @@ "type": { "kind": "struct", "fields": [ + { + "name": "key", + "type": { + "defined": "Key" + } + }, { "name": "frozen", "type": "bool" @@ -1030,24 +1080,6 @@ ] } }, - { - "name": "PluginHeader", - "type": { - "kind": "struct", - "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, - { - "name": "pluginMapOffset", - "type": "u64" - } - ] - } - }, { "name": "Plugin", "type": { @@ -1101,6 +1133,15 @@ { "name": "Owner" }, + { + "name": "Pubkey", + "fields": [ + { + "name": "address", + "type": "publicKey" + } + ] + }, { "name": "Permanent", "fields": [ @@ -1195,6 +1236,9 @@ }, { "name": "PluginRegistry" + }, + { + "name": "Delegate" } ] } @@ -1242,11 +1286,16 @@ }, { "code": 5, + "name": "NumericalOverflow", + "msg": "Numerical Overflow" + }, + { + "code": 6, "name": "IncorrectAccount", "msg": "Incorrect account" }, { - "code": 6, + "code": 7, "name": "IncorrectAssetHash", "msg": "Incorrect asset hash" } diff --git a/programs/mpl-asset/src/plugins/delegate.rs b/programs/mpl-asset/src/plugins/delegate.rs index df980666..e8b18d43 100644 --- a/programs/mpl-asset/src/plugins/delegate.rs +++ b/programs/mpl-asset/src/plugins/delegate.rs @@ -1,11 +1,28 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use shank::ShankAccount; use crate::{state::Key, utils::DataBlob}; +#[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub struct Delegate { - key: Key, // 1 - frozen: bool, // 1 + pub key: Key, // 1 + pub frozen: bool, // 1 +} + +impl Delegate { + pub fn new() -> Self { + Self { + key: Key::Delegate, + frozen: false, + } + } +} + +impl Default for Delegate { + fn default() -> Self { + Self::new() + } } impl DataBlob for Delegate { diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index 618b62c7..b8c1daa7 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -4,15 +4,13 @@ mod utils; pub use collection::*; pub use royalties::*; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, -}; + +use shank::ShankAccount; pub use utils::*; use borsh::{BorshDeserialize, BorshSerialize}; use crate::{ - error::MplAssetError, state::{Authority, Key}, utils::DataBlob, }; @@ -50,8 +48,24 @@ pub struct RegistryData { #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct RegistryRecord { + pub key: Key, + pub data: RegistryData, +} + +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +pub struct ExternalPluginRecord { + pub authority: Authority, + pub data: RegistryData, +} + +#[repr(C)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct PluginRegistry { - pub registry: Vec<(Key, RegistryData)>, // 4 + pub key: Key, // 1 + pub registry: Vec, // 4 + pub external_plugins: Vec, // 4 } impl PluginRegistry { @@ -73,11 +87,11 @@ impl PluginRegistry { impl DataBlob for PluginRegistry { fn get_initial_size() -> usize { - 4 + 9 } fn get_size(&self) -> usize { - 4 //TODO: Fix this + 9 //TODO: Fix this } } diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index f3068280..30ab35b4 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -10,7 +10,7 @@ use crate::{ utils::DataBlob, }; -use super::{Plugin, PluginRegistry, RegistryData}; +use super::{Plugin, PluginRegistry, RegistryData, RegistryRecord}; /// Create plugin header and registry if it doesn't exist pub fn create_idempotent<'a>( @@ -72,8 +72,13 @@ pub fn fetch_plugin( // Find the plugin in the registry. let plugin_data = registry .iter() - .find(|(plugin_key, _)| *plugin_key == key) - .map(|(_, data)| data) + .find( + |RegistryRecord { + key: plugin_key, + data: _, + }| *plugin_key == key, + ) + .map(|RegistryRecord { key: _, data }| data) .ok_or(MplAssetError::PluginNotFound)?; // Deserialize the plugin. @@ -92,7 +97,10 @@ pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { let PluginRegistry { registry, .. } = PluginRegistry::load(account, header.plugin_registry_offset)?; - Ok(registry.iter().map(|(key, _)| *key).collect()) + Ok(registry + .iter() + .map(|registry_record| registry_record.key) + .collect()) } /// Add a plugin into the registry @@ -120,15 +128,19 @@ pub fn add_plugin<'a>( Plugin::Delegate(delegate) => (delegate.get_size(), Key::Delegate), Plugin::Inscription => todo!(), }; - - if let Some((_, registry_data)) = plugin_registry - .registry - .iter_mut() - .find(|(search_key, registry_data)| search_key == &key) - { - registry_data.authorities.push(authority); - - let authority_bytes = authority.try_to_vec()?; + let authority_bytes = authority.try_to_vec()?; + + if let Some(RegistryRecord { + key: _, + data: registry_data, + }) = plugin_registry.registry.iter_mut().find( + |RegistryRecord { + key: search_key, + data: _, + }| search_key == &key, + ) { + solana_program::msg!("Adding authority to existing plugin"); + registry_data.authorities.push(authority.clone()); let new_size = account .data_len() @@ -138,35 +150,44 @@ pub fn add_plugin<'a>( plugin_registry.save(account, header.plugin_registry_offset); } else { - let authority_bytes = authority.try_to_vec()?; + solana_program::msg!("Adding new plugin"); - let new_size = account - .data_len() - .checked_add(plugin_size) + let old_registry_offset = header.plugin_registry_offset; + let registry_data = RegistryData { + offset: old_registry_offset, + authorities: vec![authority], + }; + + //TODO: There's probably a better way to get the size without having to serialize the registry data. + let size_increase = plugin_size + .checked_add(Key::get_initial_size()) .ok_or(MplAssetError::NumericalOverflow)? - .checked_add(authority_bytes.len()) + .checked_add(registry_data.clone().try_to_vec()?.len()) .ok_or(MplAssetError::NumericalOverflow)?; - let old_registry_offset = header.plugin_registry_offset; let new_registry_offset = header .plugin_registry_offset .checked_add(plugin_size) - .ok_or(MplAssetError::NumericalOverflow)? - .checked_add(authority_bytes.len()) .ok_or(MplAssetError::NumericalOverflow)?; header.plugin_registry_offset = new_registry_offset; - plugin_registry.registry.push(( + + plugin_registry.registry.push(RegistryRecord { key, - RegistryData { - offset: old_registry_offset, - authorities: vec![authority], - }, - )); + data: registry_data.clone(), + }); + + let new_size = account + .data_len() + .checked_add(size_increase) + .ok_or(MplAssetError::NumericalOverflow)?; - resize_or_reallocate_account_raw(account, payer, system_program, new_size); + solana_program::msg!("Resizing account"); + resize_or_reallocate_account_raw(account, payer, system_program, new_size)?; - header.save(account, asset.get_size()); + solana_program::msg!("Saving plugin header"); + header.save(account, asset.get_size())?; + solana_program::msg!("Saving plugin"); match plugin { Plugin::Reserved => todo!(), Plugin::Royalties => todo!(), @@ -175,7 +196,9 @@ pub fn add_plugin<'a>( Plugin::Delegate(delegate) => delegate.save(account, old_registry_offset), Plugin::Inscription => todo!(), }; - plugin_registry.save(account, new_registry_offset); + + solana_program::msg!("Saving plugin registry"); + plugin_registry.save(account, new_registry_offset)?; } Ok(()) diff --git a/programs/mpl-asset/src/processor/delegate.rs b/programs/mpl-asset/src/processor/delegate.rs index 97b2b448..92070baa 100644 --- a/programs/mpl-asset/src/processor/delegate.rs +++ b/programs/mpl-asset/src/processor/delegate.rs @@ -1,29 +1,45 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ - error::MplAssetError, - instruction::accounts::{CreateAccounts, DelegateAccounts}, - plugins::create_idempotent, - state::{Asset, Compressible, DataState, HashedAsset, Key}, + instruction::accounts::DelegateAccounts, + plugins::{add_plugin, create_idempotent, Delegate, Plugin}, + state::Authority, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct DelegateArgs {} -pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], args: DelegateArgs) -> ProgramResult { +pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], _args: DelegateArgs) -> ProgramResult { let ctx = DelegateAccounts::context(accounts)?; + assert_signer(ctx.accounts.owner)?; + let payer = match ctx.accounts.payer { + Some(payer) => { + assert_signer(payer)?; + payer + } + None => ctx.accounts.owner, + }; + create_idempotent( ctx.accounts.asset_address, ctx.accounts.owner, ctx.accounts.system_program, )?; - Ok(()) + let plugin = Plugin::Delegate(Delegate::new()); + + solana_program::msg!("Add plugin"); + add_plugin( + &plugin, + Authority::Pubkey { + address: *ctx.accounts.delegate.key, + }, + ctx.accounts.asset_address, + payer, + ctx.accounts.system_program, + ) } diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 5e9eebb4..c5649351 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -25,6 +25,7 @@ pub enum DataState { #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub enum Authority { Owner, + Pubkey { address: Pubkey }, Permanent { address: Pubkey }, SameAs { plugin: Plugin }, Collection, @@ -60,6 +61,12 @@ pub enum Key { Delegate, } +impl Key { + pub fn get_initial_size() -> usize { + 1 + } +} + #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub enum MigrationLevel { diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index 7f138c7a..1caf91b8 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -1,8 +1,9 @@ use crate::{state::Key, utils::DataBlob}; use borsh::{BorshDeserialize, BorshSerialize}; +use shank::ShankAccount; #[repr(C)] -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct PluginHeader { pub key: Key, pub plugin_registry_offset: usize, // 8 From a71e0a9449087f15f434b21319202c9d3c8351f0 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sun, 18 Feb 2024 11:15:43 -0700 Subject: [PATCH 14/20] Splitting trait. --- clients/js/src/generated/types/plugin.ts | 22 +------- clients/rust/src/generated/types/plugin.rs | 3 -- idls/mpl_asset_program.json | 9 ---- programs/mpl-asset/src/plugins/delegate.rs | 8 ++- programs/mpl-asset/src/plugins/mod.rs | 54 +++---------------- programs/mpl-asset/src/plugins/utils.rs | 11 +--- programs/mpl-asset/src/processor/transfer.rs | 4 +- programs/mpl-asset/src/state/asset.rs | 7 ++- programs/mpl-asset/src/state/hashed_asset.rs | 4 +- programs/mpl-asset/src/state/mod.rs | 3 ++ programs/mpl-asset/src/state/plugin_header.rs | 4 +- programs/mpl-asset/src/state/traits.rs | 44 +++++++++++++++ programs/mpl-asset/src/utils.rs | 34 +----------- 13 files changed, 77 insertions(+), 130 deletions(-) create mode 100644 programs/mpl-asset/src/state/traits.rs diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index bc7fa052..889abca0 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -20,33 +20,24 @@ import { Delegate, DelegateArgs, getDelegateSerializer } from '.'; export type Plugin = | { __kind: 'Reserved' } | { __kind: 'Royalties' } - | { __kind: 'MasterEdition' } - | { __kind: 'PrintEdition' } - | { __kind: 'Delegate'; fields: [Delegate] } - | { __kind: 'Inscription' }; + | { __kind: 'Delegate'; fields: [Delegate] }; export type PluginArgs = | { __kind: 'Reserved' } | { __kind: 'Royalties' } - | { __kind: 'MasterEdition' } - | { __kind: 'PrintEdition' } - | { __kind: 'Delegate'; fields: [DelegateArgs] } - | { __kind: 'Inscription' }; + | { __kind: 'Delegate'; fields: [DelegateArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( [ ['Reserved', unit()], ['Royalties', unit()], - ['MasterEdition', unit()], - ['PrintEdition', unit()], [ 'Delegate', struct>([ ['fields', tuple([getDelegateSerializer()])], ]), ], - ['Inscription', unit()], ], { description: 'Plugin' } ) as Serializer; @@ -59,19 +50,10 @@ export function plugin( export function plugin( kind: 'Royalties' ): GetDataEnumKind; -export function plugin( - kind: 'MasterEdition' -): GetDataEnumKind; -export function plugin( - kind: 'PrintEdition' -): GetDataEnumKind; export function plugin( kind: 'Delegate', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; -export function plugin( - kind: 'Inscription' -): GetDataEnumKind; export function plugin( kind: K, data?: any diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 3f54bda4..c2241d62 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -14,8 +14,5 @@ use borsh::BorshSerialize; pub enum Plugin { Reserved, Royalties, - MasterEdition, - PrintEdition, Delegate(Delegate), - Inscription, } diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index c5f55b0d..ac3b05ce 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -1091,12 +1091,6 @@ { "name": "Royalties" }, - { - "name": "MasterEdition" - }, - { - "name": "PrintEdition" - }, { "name": "Delegate", "fields": [ @@ -1104,9 +1098,6 @@ "defined": "Delegate" } ] - }, - { - "name": "Inscription" } ] } diff --git a/programs/mpl-asset/src/plugins/delegate.rs b/programs/mpl-asset/src/plugins/delegate.rs index e8b18d43..0dc05bb2 100644 --- a/programs/mpl-asset/src/plugins/delegate.rs +++ b/programs/mpl-asset/src/plugins/delegate.rs @@ -1,7 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use shank::ShankAccount; -use crate::{state::Key, utils::DataBlob}; +use crate::{ + state::Key, + state::{DataBlob, SolanaAccount}, +}; #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] @@ -33,7 +35,9 @@ impl DataBlob for Delegate { fn get_size(&self) -> usize { 2 } +} +impl SolanaAccount for Delegate { fn key() -> crate::state::Key { Key::Delegate } diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index b8c1daa7..ee03aac6 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -10,35 +10,16 @@ pub use utils::*; use borsh::{BorshDeserialize, BorshSerialize}; -use crate::{ - state::{Authority, Key}, - utils::DataBlob, -}; - -// macro_rules! plugin_instruction { -// ($a:expr, $b:expr) => { -// (($a as u32) << 16u32) | ($b as u32) -// }; -// } +use crate::state::{Authority, DataBlob, Key, SolanaAccount}; #[repr(u16)] #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] pub enum Plugin { Reserved, Royalties, - MasterEdition, - PrintEdition, - Delegate, - Inscription, + Delegate(Delegate), } -// #[repr(u32)] -// pub enum NonFungibleInstructions { -// Create = plugin_instruction!(Plugin::Metadata, 0), -// Transfer = plugin_instruction!(Plugin::Metadata, 1), -// Burn = plugin_instruction!(Plugin::Metadata, 2), -//} - #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct RegistryData { @@ -68,23 +49,6 @@ pub struct PluginRegistry { pub external_plugins: Vec, // 4 } -impl PluginRegistry { - pub fn load(account: &AccountInfo, offset: usize) -> Result { - let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; - PluginRegistry::deserialize(&mut bytes).map_err(|error| { - msg!("Error: {}", error); - MplAssetError::DeserializationError.into() - }) - } - - pub fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { - borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { - msg!("Error: {}", error); - MplAssetError::SerializationError.into() - }) - } -} - impl DataBlob for PluginRegistry { fn get_initial_size() -> usize { 9 @@ -95,12 +59,8 @@ impl DataBlob for PluginRegistry { } } -// pub trait PluginTrait -// where -// Self: BorshSerialize + BorshDeserialize + Clone + std::fmt::Debug + Sized, -// { -// fn get_plugin() -> Plugin; -// fn get_authority(&self) -> Option { -// None -// } -// } +impl SolanaAccount for PluginRegistry { + fn key() -> Key { + Key::PluginRegistry + } +} diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 30ab35b4..aded7d70 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -6,8 +6,7 @@ use solana_program::{ use crate::{ error::MplAssetError, - state::{Asset, Authority, Key, PluginHeader}, - utils::DataBlob, + state::{Asset, Authority, DataBlob, Key, PluginHeader, SolanaAccount}, }; use super::{Plugin, PluginRegistry, RegistryData, RegistryRecord}; @@ -123,10 +122,7 @@ pub fn add_plugin<'a>( let (plugin_size, key) = match plugin { Plugin::Reserved => todo!(), Plugin::Royalties => todo!(), - Plugin::MasterEdition => todo!(), - Plugin::PrintEdition => todo!(), Plugin::Delegate(delegate) => (delegate.get_size(), Key::Delegate), - Plugin::Inscription => todo!(), }; let authority_bytes = authority.try_to_vec()?; @@ -191,10 +187,7 @@ pub fn add_plugin<'a>( match plugin { Plugin::Reserved => todo!(), Plugin::Royalties => todo!(), - Plugin::MasterEdition => todo!(), - Plugin::PrintEdition => todo!(), - Plugin::Delegate(delegate) => delegate.save(account, old_registry_offset), - Plugin::Inscription => todo!(), + Plugin::Delegate(delegate) => delegate.save(account, old_registry_offset)?, }; solana_program::msg!("Saving plugin registry"); diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 6a8cf5e5..34bdd859 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -5,8 +5,8 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, progr use crate::{ error::MplAssetError, instruction::accounts::TransferAccounts, - state::{Asset, Compressible, CompressionProof, HashedAsset, Key}, - utils::{load_key, DataBlob}, + state::{Asset, Compressible, CompressionProof, HashedAsset, Key, SolanaAccount}, + utils::load_key, }; #[repr(C)] diff --git a/programs/mpl-asset/src/state/asset.rs b/programs/mpl-asset/src/state/asset.rs index c67fc0aa..8ca6e494 100644 --- a/programs/mpl-asset/src/state/asset.rs +++ b/programs/mpl-asset/src/state/asset.rs @@ -2,10 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; use solana_program::{keccak, program_error::ProgramError, pubkey::Pubkey}; -use crate::{ - state::{CompressionProof, Key}, - utils::DataBlob, -}; +use crate::state::{CompressionProof, DataBlob, Key, SolanaAccount}; use super::Compressible; @@ -38,7 +35,9 @@ impl DataBlob for Asset { fn get_size(&self) -> usize { Asset::BASE_LENGTH + self.name.len() + self.uri.len() } +} +impl SolanaAccount for Asset { fn key() -> Key { Key::Asset } diff --git a/programs/mpl-asset/src/state/hashed_asset.rs b/programs/mpl-asset/src/state/hashed_asset.rs index 0c3931b8..d63a3cf7 100644 --- a/programs/mpl-asset/src/state/hashed_asset.rs +++ b/programs/mpl-asset/src/state/hashed_asset.rs @@ -1,7 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; -use crate::{state::Key, utils::DataBlob}; +use crate::state::{DataBlob, Key, SolanaAccount}; #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount, PartialEq, Eq)] pub struct HashedAsset { @@ -28,7 +28,9 @@ impl DataBlob for HashedAsset { fn get_size(&self) -> usize { HashedAsset::LENGTH } +} +impl SolanaAccount for HashedAsset { fn key() -> Key { Key::HashedAsset } diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index c5649351..771fe0fc 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -8,6 +8,9 @@ mod plugin_header; use num_derive::FromPrimitive; pub use plugin_header::*; +mod traits; +pub use traits::*; + use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; diff --git a/programs/mpl-asset/src/state/plugin_header.rs b/programs/mpl-asset/src/state/plugin_header.rs index 1caf91b8..2fa98b66 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -1,4 +1,4 @@ -use crate::{state::Key, utils::DataBlob}; +use crate::state::{DataBlob, Key, SolanaAccount}; use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; @@ -17,7 +17,9 @@ impl DataBlob for PluginHeader { fn get_size(&self) -> usize { 1 + 8 } +} +impl SolanaAccount for PluginHeader { fn key() -> Key { Key::PluginHeader } diff --git a/programs/mpl-asset/src/state/traits.rs b/programs/mpl-asset/src/state/traits.rs new file mode 100644 index 00000000..85832acc --- /dev/null +++ b/programs/mpl-asset/src/state/traits.rs @@ -0,0 +1,44 @@ +use crate::{error::MplAssetError, state::Key}; +use borsh::{BorshDeserialize, BorshSerialize}; +use num_traits::FromPrimitive; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; +use solana_program::msg; +use solana_program::program_error::ProgramError; + +pub trait DataBlob: BorshSerialize + BorshDeserialize { + fn get_initial_size() -> usize; + fn get_size(&self) -> usize; +} + +pub trait SolanaAccount: BorshSerialize + BorshDeserialize { + fn key() -> Key; + + fn load(account: &AccountInfo, offset: usize) -> Result { + let key = load_key(account, offset)?; + + if key != Self::key() { + return Err(MplAssetError::DeserializationError.into()); + } + + let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; + Self::deserialize(&mut bytes).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::DeserializationError.into() + }) + } + + fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { + borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::SerializationError.into() + }) + } +} + +pub fn load_key(account: &AccountInfo, offset: usize) -> Result { + let key = Key::from_u8((*account.data).borrow()[offset]) + .ok_or(MplAssetError::DeserializationError)?; + + Ok(key) +} diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs index 7df899c1..0e8f8412 100644 --- a/programs/mpl-asset/src/utils.rs +++ b/programs/mpl-asset/src/utils.rs @@ -1,37 +1,7 @@ -use crate::{error::MplAssetError, state::Key}; -use borsh::{BorshDeserialize, BorshSerialize}; use num_traits::FromPrimitive; -use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; -use solana_program::msg; -use solana_program::program_error::ProgramError; - -pub trait DataBlob: BorshSerialize + BorshDeserialize { - fn get_initial_size() -> usize; - fn get_size(&self) -> usize; - fn key() -> Key; - - fn load(account: &AccountInfo, offset: usize) -> Result { - let key = load_key(account, offset)?; - - if key != Self::key() { - return Err(MplAssetError::DeserializationError.into()); - } +use solana_program::{account_info::AccountInfo, program_error::ProgramError}; - let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; - Self::deserialize(&mut bytes).map_err(|error| { - msg!("Error: {}", error); - MplAssetError::DeserializationError.into() - }) - } - - fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { - borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { - msg!("Error: {}", error); - MplAssetError::SerializationError.into() - }) - } -} +use crate::{error::MplAssetError, state::Key}; pub fn load_key(account: &AccountInfo, offset: usize) -> Result { let key = Key::from_u8((*account.data).borrow()[offset]) From 4965b183c82c63ccf05d847536eae0b49f94042d Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:57:16 -0800 Subject: [PATCH 15/20] Moved proof verification to helper and added wrap function --- programs/mpl-asset/src/processor/transfer.rs | 22 +++++-------- programs/mpl-asset/src/state/asset.rs | 33 +++++++++++++++++--- programs/mpl-asset/src/state/mod.rs | 7 +---- programs/mpl-asset/src/state/traits.rs | 11 +++---- programs/mpl-asset/src/utils.rs | 8 +++-- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 34bdd859..6d6ca326 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, program::invoke}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplAssetError, @@ -27,22 +27,14 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) match load_key(ctx.accounts.asset_address, 0)? { Key::HashedAsset => { - // TODO: Needs to be in helper. - // Check that arguments passed in result in on-chain hash. - let mut asset = Asset::from(args.compression_proof); - let args_asset_hash = asset.hash()?; - let current_account_hash = HashedAsset::load(ctx.accounts.asset_address, 0)?.hash; - if args_asset_hash != current_account_hash { - return Err(MplAssetError::IncorrectAssetHash.into()); - } - - // TODO: Needs to be in helper. - // Update owner and send Noop instruction. + let mut asset = + Asset::verify_proof(ctx.accounts.asset_address, args.compression_proof)?; + asset.owner = *ctx.accounts.new_owner.key; - let serialized_data = asset.try_to_vec()?; - invoke(&spl_noop::instruction(serialized_data), &[])?; - // Make a new hashed asset with updated owner. + asset.wrap()?; + + // Make a new hashed asset with updated owner and save to account. HashedAsset::new(asset.hash()?).save(ctx.accounts.asset_address, 0) } Key::Asset => { diff --git a/programs/mpl-asset/src/state/asset.rs b/programs/mpl-asset/src/state/asset.rs index 8ca6e494..e8cdfc55 100644 --- a/programs/mpl-asset/src/state/asset.rs +++ b/programs/mpl-asset/src/state/asset.rs @@ -1,10 +1,14 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankAccount; -use solana_program::{keccak, program_error::ProgramError, pubkey::Pubkey}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, keccak, program::invoke, + program_error::ProgramError, pubkey::Pubkey, +}; -use crate::state::{CompressionProof, DataBlob, Key, SolanaAccount}; - -use super::Compressible; +use crate::{ + error::MplAssetError, + state::{Compressible, CompressionProof, DataBlob, HashedAsset, Key, SolanaAccount}, +}; #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, ShankAccount)] pub struct Asset { @@ -17,14 +21,33 @@ pub struct Asset { impl Asset { pub const BASE_LENGTH: usize = 1 + 32 + 32 + 4 + 4; + + // Check that a compression proof results in same on-chain hash. + pub fn verify_proof( + hashed_asset: &AccountInfo, + compression_proof: CompressionProof, + ) -> Result { + let asset = Self::from(compression_proof); + let asset_hash = asset.hash()?; + let current_account_hash = HashedAsset::load(hashed_asset, 0)?.hash; + if asset_hash != current_account_hash { + return Err(MplAssetError::IncorrectAssetHash.into()); + } + + Ok(asset) + } } impl Compressible for Asset { fn hash(&self) -> Result<[u8; 32], ProgramError> { let serialized_data = self.try_to_vec()?; - Ok(keccak::hash(serialized_data.as_slice()).to_bytes()) } + + fn wrap(&self) -> ProgramResult { + let serialized_data = self.try_to_vec()?; + invoke(&spl_noop::instruction(serialized_data), &[]) + } } impl DataBlob for Asset { diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 771fe0fc..756f67ad 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -47,12 +47,7 @@ pub enum ExtraAccounts { owner_pda: Option, }, } - -pub trait Compressible { - fn hash(&self) -> Result<[u8; 32], ProgramError>; -} - -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, FromPrimitive)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, FromPrimitive)] pub enum Key { Uninitialized, Asset, diff --git a/programs/mpl-asset/src/state/traits.rs b/programs/mpl-asset/src/state/traits.rs index 85832acc..7a284e60 100644 --- a/programs/mpl-asset/src/state/traits.rs +++ b/programs/mpl-asset/src/state/traits.rs @@ -1,6 +1,5 @@ -use crate::{error::MplAssetError, state::Key}; +use crate::{error::MplAssetError, state::Key, utils::load_key}; use borsh::{BorshDeserialize, BorshSerialize}; -use num_traits::FromPrimitive; use solana_program::account_info::AccountInfo; use solana_program::entrypoint::ProgramResult; use solana_program::msg; @@ -36,9 +35,7 @@ pub trait SolanaAccount: BorshSerialize + BorshDeserialize { } } -pub fn load_key(account: &AccountInfo, offset: usize) -> Result { - let key = Key::from_u8((*account.data).borrow()[offset]) - .ok_or(MplAssetError::DeserializationError)?; - - Ok(key) +pub trait Compressible { + fn hash(&self) -> Result<[u8; 32], ProgramError>; + fn wrap(&self) -> ProgramResult; } diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs index 0e8f8412..be1a516d 100644 --- a/programs/mpl-asset/src/utils.rs +++ b/programs/mpl-asset/src/utils.rs @@ -1,7 +1,9 @@ -use num_traits::FromPrimitive; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; - use crate::{error::MplAssetError, state::Key}; +use num_traits::FromPrimitive; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_error::ProgramError, +}; pub fn load_key(account: &AccountInfo, offset: usize) -> Result { let key = Key::from_u8((*account.data).borrow()[offset]) From 51a1cca838de26dc4f185b653a4bea6ba40a1121 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sun, 18 Feb 2024 17:45:04 -0700 Subject: [PATCH 16/20] Adding transfer test and rounding out delegate. --- clients/js/src/generated/errors/mplAsset.ts | 52 +++++++++++ .../js/src/generated/instructions/transfer.ts | 9 +- clients/js/test/create.test.ts | 4 +- clients/js/test/delegate.test.ts | 51 ++++++++-- clients/js/test/transfer.test.ts | 93 +++++++++++++++++++ .../rust/src/generated/errors/mpl_asset.rs | 12 +++ .../src/generated/instructions/transfer.rs | 15 +-- idls/mpl_asset_program.json | 24 ++++- programs/mpl-asset/src/error.rs | 16 ++++ programs/mpl-asset/src/plugins/utils.rs | 19 ++-- programs/mpl-asset/src/processor/delegate.rs | 7 +- programs/mpl-asset/src/processor/freeze.rs | 34 ++++++- programs/mpl-asset/src/processor/thaw.rs | 34 ++++++- programs/mpl-asset/src/processor/transfer.rs | 48 +++++++++- programs/mpl-asset/src/utils.rs | 40 +++++++- 15 files changed, 400 insertions(+), 58 deletions(-) create mode 100644 clients/js/test/transfer.test.ts diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index 36cc5d75..a34dc744 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -119,6 +119,58 @@ export class IncorrectAssetHashError extends ProgramError { codeToErrorMap.set(0x7, IncorrectAssetHashError); nameToErrorMap.set('IncorrectAssetHash', IncorrectAssetHashError); +/** InvalidPlugin: Invalid Plugin */ +export class InvalidPluginError extends ProgramError { + readonly name: string = 'InvalidPlugin'; + + readonly code: number = 0x8; // 8 + + constructor(program: Program, cause?: Error) { + super('Invalid Plugin', program, cause); + } +} +codeToErrorMap.set(0x8, InvalidPluginError); +nameToErrorMap.set('InvalidPlugin', InvalidPluginError); + +/** InvalidAuthority: Invalid Authority */ +export class InvalidAuthorityError extends ProgramError { + readonly name: string = 'InvalidAuthority'; + + readonly code: number = 0x9; // 9 + + constructor(program: Program, cause?: Error) { + super('Invalid Authority', program, cause); + } +} +codeToErrorMap.set(0x9, InvalidAuthorityError); +nameToErrorMap.set('InvalidAuthority', InvalidAuthorityError); + +/** AssetIsFrozen: Cannot transfer a frozen asset */ +export class AssetIsFrozenError extends ProgramError { + readonly name: string = 'AssetIsFrozen'; + + readonly code: number = 0xa; // 10 + + constructor(program: Program, cause?: Error) { + super('Cannot transfer a frozen asset', program, cause); + } +} +codeToErrorMap.set(0xa, AssetIsFrozenError); +nameToErrorMap.set('AssetIsFrozen', AssetIsFrozenError); + +/** MissingCompressionProof: Missing compression proof */ +export class MissingCompressionProofError extends ProgramError { + readonly name: string = 'MissingCompressionProof'; + + readonly code: number = 0xb; // 11 + + constructor(program: Program, cause?: Error) { + super('Missing compression proof', program, cause); + } +} +codeToErrorMap.set(0xb, MissingCompressionProofError); +nameToErrorMap.set('MissingCompressionProof', MissingCompressionProofError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/transfer.ts b/clients/js/src/generated/instructions/transfer.ts index 4972eb05..6e87aabd 100644 --- a/clients/js/src/generated/instructions/transfer.ts +++ b/clients/js/src/generated/instructions/transfer.ts @@ -8,6 +8,8 @@ import { Context, + Option, + OptionOrNullable, Pda, PublicKey, Signer, @@ -17,6 +19,7 @@ import { import { Serializer, mapSerializer, + option, struct, u8, } from '@metaplex-foundation/umi/serializers'; @@ -50,11 +53,11 @@ export type TransferInstructionAccounts = { // Data. export type TransferInstructionData = { discriminator: number; - compressionProof: CompressionProof; + compressionProof: Option; }; export type TransferInstructionDataArgs = { - compressionProof: CompressionProofArgs; + compressionProof: OptionOrNullable; }; export function getTransferInstructionDataSerializer(): Serializer< @@ -69,7 +72,7 @@ export function getTransferInstructionDataSerializer(): Serializer< struct( [ ['discriminator', u8()], - ['compressionProof', getCompressionProofSerializer()], + ['compressionProof', option(getCompressionProofSerializer())], ], { description: 'TransferInstructionData' } ), diff --git a/clients/js/test/create.test.ts b/clients/js/test/create.test.ts index 89a9b1ba..5e10edc2 100644 --- a/clients/js/test/create.test.ts +++ b/clients/js/test/create.test.ts @@ -19,7 +19,7 @@ test('it can create a new asset in account state', async (t) => { // Then an account was created with the correct data. const asset = await fetchAsset(umi, assetAddress.publicKey); - console.log("Account State:", asset); + // console.log("Account State:", asset); t.like(asset, { publicKey: assetAddress.publicKey, updateAuthority: umi.identity.publicKey, @@ -56,7 +56,7 @@ test('it can create a new asset in ledger state', async (t) => { const { data } = tx.meta.innerInstructions[0].instructions[0]; // console.log(base58.deserialize(data)); const parsed = getAssetAccountDataSerializer().deserialize(data)[0]; - console.log("Ledger State:", parsed); + // console.log("Ledger State:", parsed); t.like(parsed, { updateAuthority: umi.identity.publicKey, owner: umi.identity.publicKey, diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/delegate.test.ts index b8ac7614..5fecb3af 100644 --- a/clients/js/test/delegate.test.ts +++ b/clients/js/test/delegate.test.ts @@ -1,10 +1,26 @@ import { generateSigner } from '@metaplex-foundation/umi'; import test from 'ava'; // import { base58 } from '@metaplex-foundation/umi/serializers'; -import { DataState, create, delegate, fetchAsset, getAssetAccountDataSerializer, getDelegateSerializer, getPluginHeaderAccountDataSerializer, getPluginRegistryAccountDataSerializer, } from '../src'; +import { + Authority, + DataState, + Delegate, + Key, + PluginHeaderAccountData, + PluginRegistryAccountData, + RegistryData, + RegistryRecord, + create, + delegate, + fetchAsset, + getAssetAccountDataSerializer, + getDelegateSerializer, + getPluginHeaderAccountDataSerializer, + getPluginRegistryAccountDataSerializer +} from '../src'; import { createUmi } from './_setup'; -test('it initializes the plugin system correctly', async (t) => { +test('it can delegate a new authority', async (t) => { // Given a Umi instance and a new signer. const umi = await createUmi(); const assetAddress = generateSigner(umi); @@ -20,7 +36,7 @@ test('it initializes the plugin system correctly', async (t) => { // Then an account was created with the correct data. const asset = await fetchAsset(umi, assetAddress.publicKey); - console.log("Before Plugins:\n", asset); + // console.log("Before Plugins:\n", asset); const assetData = getAssetAccountDataSerializer().serialize(asset); await delegate(umi, { @@ -32,11 +48,30 @@ test('it initializes the plugin system correctly', async (t) => { const pluginData = await umi.rpc.getAccount(assetAddress.publicKey); if (pluginData.exists) { const pluginHeader = getPluginHeaderAccountDataSerializer().deserialize(pluginData.data, assetData.length)[0]; - console.log("After Plugins:\n", pluginHeader); - const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(pluginData.data, Number(pluginHeader.pluginRegistryOffset)); - console.log(JSON.stringify(pluginRegistry, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); - const delegatePlugin = getDelegateSerializer().deserialize(pluginData.data, Number(pluginRegistry[0].registry[0].data.offset)); - console.log(delegatePlugin); + // console.log("After Plugins:\n", pluginHeader); + t.like(pluginHeader, { + key: Key.PluginHeader, + pluginRegistryOffset: BigInt(119), + }); + const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(pluginData.data, Number(pluginHeader.pluginRegistryOffset))[0]; + // console.log(JSON.stringify(pluginRegistry, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + t.like(pluginRegistry, { + key: Key.PluginRegistry, + registry: [{ + key: Key.Delegate, + data: { + offset: BigInt(117), + authorities: [{ __kind: 'Pubkey', address: delegateAddress.publicKey }], + }, + }], + externalPlugins: [], + }); + const delegatePlugin = getDelegateSerializer().deserialize(pluginData.data, Number(pluginRegistry.registry[0].data.offset))[0]; + // console.log(delegatePlugin); + t.like(delegatePlugin, { + key: Key.Delegate, + frozen: false, + }); } else { t.fail("Plugin data not found"); } diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts new file mode 100644 index 00000000..67f5e3ec --- /dev/null +++ b/clients/js/test/transfer.test.ts @@ -0,0 +1,93 @@ +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +// import { base58 } from '@metaplex-foundation/umi/serializers'; +import { Asset, DataState, create, fetchAsset, transfer } from '../src'; +import { createUmi } from './_setup'; + +test('it can transfer an asset as the owner', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const newOwner = 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); + + // Then an account was created with the correct data. + const beforeAsset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", beforeAsset); + t.like(beforeAsset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); + + await transfer(umi, { + assetAddress: assetAddress.publicKey, + newOwner: newOwner.publicKey, + compressionProof: null + }).sendAndConfirm(umi); + + const afterAsset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", afterAsset); + t.like(afterAsset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: newOwner.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); +}); + +test('it cannot transfer an asset if not the owner', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const newOwner = generateSigner(umi); + const attacker = 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); + + // Then an account was created with the correct data. + const beforeAsset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", beforeAsset); + t.like(beforeAsset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); + + const result = transfer(umi, { + assetAddress: assetAddress.publicKey, + newOwner: newOwner.publicKey, + compressionProof: null, + authority: attacker, + }).sendAndConfirm(umi); + + t.throwsAsync(result, { name: 'InvalidAuthority' }) + + const afterAsset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", afterAsset); + t.like(afterAsset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); +}); diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index 3dbe990d..d1019602 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -34,6 +34,18 @@ pub enum MplAssetError { /// 7 (0x7) - Incorrect asset hash #[error("Incorrect asset hash")] IncorrectAssetHash, + /// 8 (0x8) - Invalid Plugin + #[error("Invalid Plugin")] + InvalidPlugin, + /// 9 (0x9) - Invalid Authority + #[error("Invalid Authority")] + InvalidAuthority, + /// 10 (0xA) - Cannot transfer a frozen asset + #[error("Cannot transfer a frozen asset")] + AssetIsFrozen, + /// 11 (0xB) - Missing compression proof + #[error("Missing compression proof")] + MissingCompressionProof, } impl solana_program::program_error::PrintProgramError for MplAssetError { diff --git a/clients/rust/src/generated/instructions/transfer.rs b/clients/rust/src/generated/instructions/transfer.rs index 440385c5..dd5fbc0a 100644 --- a/clients/rust/src/generated/instructions/transfer.rs +++ b/clients/rust/src/generated/instructions/transfer.rs @@ -107,7 +107,7 @@ impl TransferInstructionData { #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TransferInstructionArgs { - pub compression_proof: CompressionProof, + pub compression_proof: Option, } /// Instruction builder. @@ -169,6 +169,7 @@ impl TransferBuilder { self.log_wrapper = log_wrapper; self } + /// `[optional argument]` #[inline(always)] pub fn compression_proof(&mut self, compression_proof: CompressionProof) -> &mut Self { self.compression_proof = Some(compression_proof); @@ -203,10 +204,7 @@ impl TransferBuilder { log_wrapper: self.log_wrapper, }; let args = TransferInstructionArgs { - compression_proof: self - .compression_proof - .clone() - .expect("compression_proof is not set"), + compression_proof: self.compression_proof.clone(), }; accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) @@ -463,6 +461,7 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { self.instruction.log_wrapper = log_wrapper; self } + /// `[optional argument]` #[inline(always)] pub fn compression_proof(&mut self, compression_proof: CompressionProof) -> &mut Self { self.instruction.compression_proof = Some(compression_proof); @@ -510,11 +509,7 @@ impl<'a, 'b> TransferCpiBuilder<'a, 'b> { signers_seeds: &[&[&[u8]]], ) -> solana_program::entrypoint::ProgramResult { let args = TransferInstructionArgs { - compression_proof: self - .instruction - .compression_proof - .clone() - .expect("compression_proof is not set"), + compression_proof: self.instruction.compression_proof.clone(), }; let instruction = TransferCpi { __program: self.instruction.__program, diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index ac3b05ce..33fbe564 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -1028,7 +1028,9 @@ { "name": "compressionProof", "type": { - "defined": "CompressionProof" + "option": { + "defined": "CompressionProof" + } } } ] @@ -1289,6 +1291,26 @@ "code": 7, "name": "IncorrectAssetHash", "msg": "Incorrect asset hash" + }, + { + "code": 8, + "name": "InvalidPlugin", + "msg": "Invalid Plugin" + }, + { + "code": 9, + "name": "InvalidAuthority", + "msg": "Invalid Authority" + }, + { + "code": 10, + "name": "AssetIsFrozen", + "msg": "Cannot transfer a frozen asset" + }, + { + "code": 11, + "name": "MissingCompressionProof", + "msg": "Missing compression proof" } ], "metadata": { diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index e9fd26f9..36e72bd1 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -35,6 +35,22 @@ pub enum MplAssetError { /// 5 - Provided data does not match asset hash. #[error("Incorrect asset hash")] IncorrectAssetHash, + + /// 8 - Invalid Plugin + #[error("Invalid Plugin")] + InvalidPlugin, + + /// 9 - Invalid Authority + #[error("Invalid Authority")] + InvalidAuthority, + + /// 10 - Asset is frozen + #[error("Cannot transfer a frozen asset")] + AssetIsFrozen, + + /// 11 - Missing compression proof + #[error("Missing compression proof")] + MissingCompressionProof, } impl PrintProgramError for MplAssetError { diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index aded7d70..f5ed1fe4 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -12,7 +12,7 @@ use crate::{ use super::{Plugin, PluginRegistry, RegistryData, RegistryRecord}; /// Create plugin header and registry if it doesn't exist -pub fn create_idempotent<'a>( +pub fn create_meta_idempotent<'a>( account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, @@ -60,7 +60,7 @@ pub fn assert_plugins_initialized(account: &AccountInfo) -> ProgramResult { pub fn fetch_plugin( account: &AccountInfo, key: Key, -) -> Result<(Vec, Plugin), ProgramError> { +) -> Result<(Vec, Plugin, usize), ProgramError> { let mut bytes: &[u8] = &(*account.data).borrow(); let asset = Asset::deserialize(&mut bytes)?; @@ -84,7 +84,7 @@ pub fn fetch_plugin( let plugin = Plugin::deserialize(&mut &(*account.data).borrow()[plugin_data.offset..])?; // Return the plugin and its authorities. - Ok((plugin_data.authorities.clone(), plugin)) + Ok((plugin_data.authorities.clone(), plugin, plugin_data.offset)) } /// Create plugin header and registry if it doesn't exist @@ -103,7 +103,7 @@ pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { } /// Add a plugin into the registry -pub fn add_plugin<'a>( +pub fn add_plugin_or_authority<'a>( plugin: &Plugin, authority: Authority, account: &AccountInfo<'a>, @@ -120,7 +120,7 @@ pub fn add_plugin<'a>( let mut plugin_registry = PluginRegistry::load(account, header.plugin_registry_offset)?; let (plugin_size, key) = match plugin { - Plugin::Reserved => todo!(), + Plugin::Reserved => return Err(MplAssetError::InvalidPlugin.into()), Plugin::Royalties => todo!(), Plugin::Delegate(delegate) => (delegate.get_size(), Key::Delegate), }; @@ -135,7 +135,6 @@ pub fn add_plugin<'a>( data: _, }| search_key == &key, ) { - solana_program::msg!("Adding authority to existing plugin"); registry_data.authorities.push(authority.clone()); let new_size = account @@ -146,8 +145,6 @@ pub fn add_plugin<'a>( plugin_registry.save(account, header.plugin_registry_offset); } else { - solana_program::msg!("Adding new plugin"); - let old_registry_offset = header.plugin_registry_offset; let registry_data = RegistryData { offset: old_registry_offset, @@ -178,19 +175,15 @@ pub fn add_plugin<'a>( .checked_add(size_increase) .ok_or(MplAssetError::NumericalOverflow)?; - solana_program::msg!("Resizing account"); resize_or_reallocate_account_raw(account, payer, system_program, new_size)?; - solana_program::msg!("Saving plugin header"); header.save(account, asset.get_size())?; - solana_program::msg!("Saving plugin"); match plugin { - Plugin::Reserved => todo!(), + Plugin::Reserved => return Err(MplAssetError::InvalidPlugin.into()), Plugin::Royalties => todo!(), Plugin::Delegate(delegate) => delegate.save(account, old_registry_offset)?, }; - solana_program::msg!("Saving plugin registry"); plugin_registry.save(account, new_registry_offset)?; } diff --git a/programs/mpl-asset/src/processor/delegate.rs b/programs/mpl-asset/src/processor/delegate.rs index 92070baa..9b9f9683 100644 --- a/programs/mpl-asset/src/processor/delegate.rs +++ b/programs/mpl-asset/src/processor/delegate.rs @@ -4,7 +4,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ instruction::accounts::DelegateAccounts, - plugins::{add_plugin, create_idempotent, Delegate, Plugin}, + plugins::{add_plugin_or_authority, create_meta_idempotent, Delegate, Plugin}, state::Authority, }; @@ -24,7 +24,7 @@ pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], _args: DelegateArgs) None => ctx.accounts.owner, }; - create_idempotent( + create_meta_idempotent( ctx.accounts.asset_address, ctx.accounts.owner, ctx.accounts.system_program, @@ -32,8 +32,7 @@ pub(crate) fn delegate<'a>(accounts: &'a [AccountInfo<'a>], _args: DelegateArgs) let plugin = Plugin::Delegate(Delegate::new()); - solana_program::msg!("Add plugin"); - add_plugin( + add_plugin_or_authority( &plugin, Authority::Pubkey { address: *ctx.accounts.delegate.key, diff --git a/programs/mpl-asset/src/processor/freeze.rs b/programs/mpl-asset/src/processor/freeze.rs index c1b88d35..f868a591 100644 --- a/programs/mpl-asset/src/processor/freeze.rs +++ b/programs/mpl-asset/src/processor/freeze.rs @@ -1,20 +1,44 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, }; use crate::{ error::MplAssetError, - instruction::accounts::CreateAccounts, - state::{Asset, Compressible, DataState, HashedAsset, Key}, + instruction::accounts::FreezeAccounts, + plugins::{fetch_plugin, Delegate, Plugin}, + state::{Key, SolanaAccount}, + utils::assert_authority, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct FreezeArgs {} -pub(crate) fn freeze<'a>(accounts: &'a [AccountInfo<'a>], args: FreezeArgs) -> ProgramResult { +pub(crate) fn freeze<'a>(accounts: &'a [AccountInfo<'a>], _args: FreezeArgs) -> ProgramResult { + let ctx = FreezeAccounts::context(accounts)?; + + assert_signer(ctx.accounts.delegate)?; + + let (authorities, mut plugin, offset) = + fetch_plugin(ctx.accounts.asset_address, Key::Delegate)?; + + assert_authority( + ctx.accounts.asset_address, + ctx.accounts.delegate, + &authorities, + )?; + + let delegate = match &mut plugin { + Plugin::Delegate(delegate) => { + delegate.frozen = true; + Ok::<&Delegate, ProgramError>(delegate) + } + _ => Err(MplAssetError::InvalidPlugin.into()), + }?; + + delegate.save(ctx.accounts.asset_address, offset)?; + Ok(()) } diff --git a/programs/mpl-asset/src/processor/thaw.rs b/programs/mpl-asset/src/processor/thaw.rs index 68885e3c..a39f76ea 100644 --- a/programs/mpl-asset/src/processor/thaw.rs +++ b/programs/mpl-asset/src/processor/thaw.rs @@ -1,20 +1,44 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, }; use crate::{ error::MplAssetError, - instruction::accounts::CreateAccounts, - state::{Asset, Compressible, DataState, HashedAsset, Key}, + instruction::accounts::ThawAccounts, + plugins::{fetch_plugin, Delegate, Plugin}, + state::{Key, SolanaAccount}, + utils::assert_authority, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct ThawArgs {} -pub(crate) fn thaw<'a>(accounts: &'a [AccountInfo<'a>], args: ThawArgs) -> ProgramResult { +pub(crate) fn thaw<'a>(accounts: &'a [AccountInfo<'a>], _args: ThawArgs) -> ProgramResult { + let ctx = ThawAccounts::context(accounts)?; + + assert_signer(ctx.accounts.delegate)?; + + let (authorities, mut plugin, offset) = + fetch_plugin(ctx.accounts.asset_address, Key::Delegate)?; + + assert_authority( + ctx.accounts.asset_address, + ctx.accounts.delegate, + &authorities, + )?; + + let delegate = match &mut plugin { + Plugin::Delegate(delegate) => { + delegate.frozen = false; + Ok::<&Delegate, ProgramError>(delegate) + } + _ => Err(MplAssetError::InvalidPlugin.into()), + }?; + + delegate.save(ctx.accounts.asset_address, offset)?; + Ok(()) } diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 34bdd859..86374097 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -1,18 +1,22 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, program::invoke}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, + program_error::ProgramError, +}; use crate::{ error::MplAssetError, instruction::accounts::TransferAccounts, - state::{Asset, Compressible, CompressionProof, HashedAsset, Key, SolanaAccount}, - utils::load_key, + plugins::{fetch_plugin, Plugin}, + state::{Asset, Compressible, CompressionProof, DataBlob, HashedAsset, Key, SolanaAccount}, + utils::{assert_authority, load_key}, }; #[repr(C)] #[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)] pub struct TransferArgs { - compression_proof: CompressionProof, + compression_proof: Option, } pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) -> ProgramResult { @@ -29,7 +33,10 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) Key::HashedAsset => { // TODO: Needs to be in helper. // Check that arguments passed in result in on-chain hash. - let mut asset = Asset::from(args.compression_proof); + let compression_proof = args + .compression_proof + .ok_or(MplAssetError::MissingCompressionProof)?; + let mut asset = Asset::from(compression_proof); let args_asset_hash = asset.hash()?; let current_account_hash = HashedAsset::load(ctx.accounts.asset_address, 0)?.hash; if args_asset_hash != current_account_hash { @@ -47,6 +54,37 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) } Key::Asset => { let mut asset = Asset::load(ctx.accounts.asset_address, 0)?; + + let mut authority_check: Result<(), ProgramError> = + Err(MplAssetError::InvalidAuthority.into()); + if asset.get_size() != ctx.accounts.asset_address.data_len() { + let (authorities, plugin, _) = + fetch_plugin(ctx.accounts.asset_address, Key::Delegate)?; + + authority_check = assert_authority( + ctx.accounts.asset_address, + ctx.accounts.authority, + &authorities, + ); + + if let Plugin::Delegate(delegate) = plugin { + if delegate.frozen { + return Err(MplAssetError::AssetIsFrozen.into()); + } + } + } + + match authority_check { + Ok(_) => Ok::<(), ProgramError>(()), + Err(_) => { + if ctx.accounts.authority.key != &asset.owner { + Err(MplAssetError::InvalidAuthority.into()) + } else { + Ok(()) + } + } + }?; + asset.owner = *ctx.accounts.new_owner.key; asset.save(ctx.accounts.asset_address, 0) } diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs index 0e8f8412..dac78e63 100644 --- a/programs/mpl-asset/src/utils.rs +++ b/programs/mpl-asset/src/utils.rs @@ -1,7 +1,12 @@ use num_traits::FromPrimitive; -use solana_program::{account_info::AccountInfo, program_error::ProgramError}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; -use crate::{error::MplAssetError, state::Key}; +use crate::{ + error::MplAssetError, + state::{Asset, Authority, Key, SolanaAccount}, +}; pub fn load_key(account: &AccountInfo, offset: usize) -> Result { let key = Key::from_u8((*account.data).borrow()[offset]) @@ -9,3 +14,34 @@ pub fn load_key(account: &AccountInfo, offset: usize) -> Result ProgramResult { + let asset = Asset::load(account, 0)?; + for auth_iter in authorities { + match auth_iter { + Authority::Owner => { + if &asset.owner == authority.key { + return Ok(()); + } + } + Authority::Pubkey { address } => { + if authority.key == address { + return Ok(()); + } + } + Authority::Permanent { address } => { + if authority.key == address { + return Ok(()); + } + } + Authority::SameAs { .. } => todo!(), + Authority::Collection => todo!(), + } + } + + Err(MplAssetError::InvalidAuthority.into()) +} From f322c17f833a72513c18bddda645705313a22555 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sun, 18 Feb 2024 19:52:44 -0700 Subject: [PATCH 17/20] Adding working delegate. --- clients/js/src/generated/types/delegate.ts | 15 ++--- clients/js/src/generated/types/index.ts | 1 + clients/js/src/generated/types/key.ts | 3 - clients/js/src/generated/types/plugin.ts | 23 ++++++-- clients/js/src/generated/types/pluginType.ts | 26 +++++++++ .../js/src/generated/types/registryRecord.ts | 15 +++-- clients/js/test/delegate.test.ts | 6 +- clients/js/test/delegateTransfer.test.ts | 56 +++++++++++++++++++ clients/rust/src/generated/types/delegate.rs | 2 - clients/rust/src/generated/types/key.rs | 3 - clients/rust/src/generated/types/mod.rs | 2 + clients/rust/src/generated/types/plugin.rs | 3 +- .../rust/src/generated/types/plugin_type.rs | 17 ++++++ .../src/generated/types/registry_record.rs | 4 +- idls/mpl_asset_program.json | 43 ++++++++------ programs/mpl-asset/src/plugins/delegate.rs | 23 ++------ programs/mpl-asset/src/plugins/mod.rs | 37 +++++++++++- programs/mpl-asset/src/plugins/royalties.rs | 4 +- programs/mpl-asset/src/plugins/utils.rs | 47 +++++++++------- programs/mpl-asset/src/processor/freeze.rs | 7 +-- programs/mpl-asset/src/processor/thaw.rs | 7 +-- programs/mpl-asset/src/processor/transfer.rs | 6 +- programs/mpl-asset/src/state/mod.rs | 4 -- 23 files changed, 244 insertions(+), 110 deletions(-) create mode 100644 clients/js/src/generated/types/pluginType.ts create mode 100644 clients/js/test/delegateTransfer.test.ts create mode 100644 clients/rust/src/generated/types/plugin_type.rs diff --git a/clients/js/src/generated/types/delegate.ts b/clients/js/src/generated/types/delegate.ts index 55ad2c58..d775b8ce 100644 --- a/clients/js/src/generated/types/delegate.ts +++ b/clients/js/src/generated/types/delegate.ts @@ -7,18 +7,13 @@ */ import { Serializer, bool, struct } from '@metaplex-foundation/umi/serializers'; -import { Key, KeyArgs, getKeySerializer } from '.'; -export type Delegate = { key: Key; frozen: boolean }; +export type Delegate = { frozen: boolean }; -export type DelegateArgs = { key: KeyArgs; frozen: boolean }; +export type DelegateArgs = Delegate; export function getDelegateSerializer(): Serializer { - return struct( - [ - ['key', getKeySerializer()], - ['frozen', bool()], - ], - { description: 'Delegate' } - ) as Serializer; + return struct([['frozen', bool()]], { + description: 'Delegate', + }) as Serializer; } diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index 1695fdb1..f0983d70 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -17,6 +17,7 @@ export * from './extraAccounts'; export * from './key'; export * from './migrationLevel'; export * from './plugin'; +export * from './pluginType'; export * from './registryData'; export * from './registryRecord'; export * from './royalties'; diff --git a/clients/js/src/generated/types/key.ts b/clients/js/src/generated/types/key.ts index 8558a0b3..e7d0fac8 100644 --- a/clients/js/src/generated/types/key.ts +++ b/clients/js/src/generated/types/key.ts @@ -12,11 +12,8 @@ export enum Key { Uninitialized, Asset, HashedAsset, - Collection, - HashedCollection, PluginHeader, PluginRegistry, - Delegate, } export type KeyArgs = Key; diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index 889abca0..e10faa68 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -15,23 +15,35 @@ import { tuple, unit, } from '@metaplex-foundation/umi/serializers'; -import { Delegate, DelegateArgs, getDelegateSerializer } from '.'; +import { + Delegate, + DelegateArgs, + Royalties, + RoyaltiesArgs, + getDelegateSerializer, + getRoyaltiesSerializer, +} from '.'; export type Plugin = | { __kind: 'Reserved' } - | { __kind: 'Royalties' } + | { __kind: 'Royalties'; fields: [Royalties] } | { __kind: 'Delegate'; fields: [Delegate] }; export type PluginArgs = | { __kind: 'Reserved' } - | { __kind: 'Royalties' } + | { __kind: 'Royalties'; fields: [RoyaltiesArgs] } | { __kind: 'Delegate'; fields: [DelegateArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( [ ['Reserved', unit()], - ['Royalties', unit()], + [ + 'Royalties', + struct>([ + ['fields', tuple([getRoyaltiesSerializer()])], + ]), + ], [ 'Delegate', struct>([ @@ -48,7 +60,8 @@ export function plugin( kind: 'Reserved' ): GetDataEnumKind; export function plugin( - kind: 'Royalties' + kind: 'Royalties', + data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; export function plugin( kind: 'Delegate', diff --git a/clients/js/src/generated/types/pluginType.ts b/clients/js/src/generated/types/pluginType.ts new file mode 100644 index 00000000..9948bd21 --- /dev/null +++ b/clients/js/src/generated/types/pluginType.ts @@ -0,0 +1,26 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, scalarEnum } from '@metaplex-foundation/umi/serializers'; + +export enum PluginType { + Reserved, + Royalties, + Delegate, +} + +export type PluginTypeArgs = PluginType; + +export function getPluginTypeSerializer(): Serializer< + PluginTypeArgs, + PluginType +> { + return scalarEnum(PluginType, { + description: 'PluginType', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/registryRecord.ts b/clients/js/src/generated/types/registryRecord.ts index 2680179a..5c7ea988 100644 --- a/clients/js/src/generated/types/registryRecord.ts +++ b/clients/js/src/generated/types/registryRecord.ts @@ -8,17 +8,20 @@ import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; import { - Key, - KeyArgs, + PluginType, + PluginTypeArgs, RegistryData, RegistryDataArgs, - getKeySerializer, + getPluginTypeSerializer, getRegistryDataSerializer, } from '.'; -export type RegistryRecord = { key: Key; data: RegistryData }; +export type RegistryRecord = { pluginType: PluginType; data: RegistryData }; -export type RegistryRecordArgs = { key: KeyArgs; data: RegistryDataArgs }; +export type RegistryRecordArgs = { + pluginType: PluginTypeArgs; + data: RegistryDataArgs; +}; export function getRegistryRecordSerializer(): Serializer< RegistryRecordArgs, @@ -26,7 +29,7 @@ export function getRegistryRecordSerializer(): Serializer< > { return struct( [ - ['key', getKeySerializer()], + ['pluginType', getPluginTypeSerializer()], ['data', getRegistryDataSerializer()], ], { description: 'RegistryRecord' } diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/delegate.test.ts index 5fecb3af..1eb03d1f 100644 --- a/clients/js/test/delegate.test.ts +++ b/clients/js/test/delegate.test.ts @@ -16,7 +16,8 @@ import { getAssetAccountDataSerializer, getDelegateSerializer, getPluginHeaderAccountDataSerializer, - getPluginRegistryAccountDataSerializer + getPluginRegistryAccountDataSerializer, + PluginType, } from '../src'; import { createUmi } from './_setup'; @@ -58,7 +59,7 @@ test('it can delegate a new authority', async (t) => { t.like(pluginRegistry, { key: Key.PluginRegistry, registry: [{ - key: Key.Delegate, + pluginType: PluginType.Delegate, data: { offset: BigInt(117), authorities: [{ __kind: 'Pubkey', address: delegateAddress.publicKey }], @@ -69,7 +70,6 @@ test('it can delegate a new authority', async (t) => { const delegatePlugin = getDelegateSerializer().deserialize(pluginData.data, Number(pluginRegistry.registry[0].data.offset))[0]; // console.log(delegatePlugin); t.like(delegatePlugin, { - key: Key.Delegate, frozen: false, }); } else { diff --git a/clients/js/test/delegateTransfer.test.ts b/clients/js/test/delegateTransfer.test.ts new file mode 100644 index 00000000..07395236 --- /dev/null +++ b/clients/js/test/delegateTransfer.test.ts @@ -0,0 +1,56 @@ +import { generateSigner } from '@metaplex-foundation/umi'; +import test from 'ava'; +// import { base58 } from '@metaplex-foundation/umi/serializers'; +import { Asset, DataState, create, delegate, fetchAsset, transfer } from '../src'; +import { createUmi } from './_setup'; + +test('it can transfer an asset as the delegate', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const newOwner = generateSigner(umi); + const delegateAddress = 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); + + // Then an account was created with the correct data. + const beforeAsset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", beforeAsset); + t.like(beforeAsset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: umi.identity.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); + + await delegate(umi, { + assetAddress: assetAddress.publicKey, + owner: umi.identity, + delegate: delegateAddress.publicKey + }).sendAndConfirm(umi); + + await transfer(umi, { + assetAddress: assetAddress.publicKey, + newOwner: newOwner.publicKey, + authority: delegateAddress, + compressionProof: null + }).sendAndConfirm(umi); + + const afterAsset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Account State:", afterAsset); + t.like(afterAsset, { + publicKey: assetAddress.publicKey, + updateAuthority: umi.identity.publicKey, + owner: newOwner.publicKey, + name: 'Test Bread', + uri: 'https://example.com/bread', + }); +}); + diff --git a/clients/rust/src/generated/types/delegate.rs b/clients/rust/src/generated/types/delegate.rs index 8a51bd3d..889bbe82 100644 --- a/clients/rust/src/generated/types/delegate.rs +++ b/clients/rust/src/generated/types/delegate.rs @@ -5,13 +5,11 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::Key; use borsh::BorshDeserialize; use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Delegate { - pub key: Key, pub frozen: bool, } diff --git a/clients/rust/src/generated/types/key.rs b/clients/rust/src/generated/types/key.rs index 956e7fea..fac43a60 100644 --- a/clients/rust/src/generated/types/key.rs +++ b/clients/rust/src/generated/types/key.rs @@ -14,9 +14,6 @@ pub enum Key { Uninitialized, Asset, HashedAsset, - Collection, - HashedCollection, PluginHeader, PluginRegistry, - Delegate, } diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index 8481e0fc..d6d3d813 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -16,6 +16,7 @@ pub(crate) mod extra_accounts; pub(crate) mod key; pub(crate) mod migration_level; pub(crate) mod plugin; +pub(crate) mod plugin_type; pub(crate) mod registry_data; pub(crate) mod registry_record; pub(crate) mod royalties; @@ -31,6 +32,7 @@ pub use self::extra_accounts::*; pub use self::key::*; pub use self::migration_level::*; pub use self::plugin::*; +pub use self::plugin_type::*; pub use self::registry_data::*; pub use self::registry_record::*; pub use self::royalties::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index c2241d62..17f2d2e7 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -6,6 +6,7 @@ //! use crate::generated::types::Delegate; +use crate::generated::types::Royalties; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -13,6 +14,6 @@ use borsh::BorshSerialize; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Plugin { Reserved, - Royalties, + Royalties(Royalties), Delegate(Delegate), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs new file mode 100644 index 00000000..5a846ec4 --- /dev/null +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum PluginType { + Reserved, + Royalties, + Delegate, +} diff --git a/clients/rust/src/generated/types/registry_record.rs b/clients/rust/src/generated/types/registry_record.rs index 741601f8..1f90655e 100644 --- a/clients/rust/src/generated/types/registry_record.rs +++ b/clients/rust/src/generated/types/registry_record.rs @@ -5,7 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! -use crate::generated::types::Key; +use crate::generated::types::PluginType; use crate::generated::types::RegistryData; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -13,6 +13,6 @@ use borsh::BorshSerialize; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RegistryRecord { - pub key: Key, + pub plugin_type: PluginType, pub data: RegistryData, } diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index 33fbe564..96e2ae5a 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -828,9 +828,9 @@ "kind": "struct", "fields": [ { - "name": "key", + "name": "pluginType", "type": { - "defined": "Key" + "defined": "PluginType" } }, { @@ -883,12 +883,6 @@ "type": { "kind": "struct", "fields": [ - { - "name": "key", - "type": { - "defined": "Key" - } - }, { "name": "frozen", "type": "bool" @@ -1091,7 +1085,12 @@ "name": "Reserved" }, { - "name": "Royalties" + "name": "Royalties", + "fields": [ + { + "defined": "Royalties" + } + ] }, { "name": "Delegate", @@ -1104,6 +1103,23 @@ ] } }, + { + "name": "PluginType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Reserved" + }, + { + "name": "Royalties" + }, + { + "name": "Delegate" + } + ] + } + }, { "name": "DataState", "type": { @@ -1218,20 +1234,11 @@ { "name": "HashedAsset" }, - { - "name": "Collection" - }, - { - "name": "HashedCollection" - }, { "name": "PluginHeader" }, { "name": "PluginRegistry" - }, - { - "name": "Delegate" } ] } diff --git a/programs/mpl-asset/src/plugins/delegate.rs b/programs/mpl-asset/src/plugins/delegate.rs index 0dc05bb2..d475bdaa 100644 --- a/programs/mpl-asset/src/plugins/delegate.rs +++ b/programs/mpl-asset/src/plugins/delegate.rs @@ -1,23 +1,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use crate::{ - state::Key, - state::{DataBlob, SolanaAccount}, -}; +use crate::state::DataBlob; #[repr(C)] -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub struct Delegate { - pub key: Key, // 1 pub frozen: bool, // 1 } impl Delegate { pub fn new() -> Self { - Self { - key: Key::Delegate, - frozen: false, - } + Self { frozen: false } } } @@ -29,16 +22,10 @@ impl Default for Delegate { impl DataBlob for Delegate { fn get_initial_size() -> usize { - 2 + 1 } fn get_size(&self) -> usize { - 2 - } -} - -impl SolanaAccount for Delegate { - fn key() -> crate::state::Key { - Key::Delegate + 1 } } diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index ee03aac6..da436067 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -6,20 +6,51 @@ pub use collection::*; pub use royalties::*; use shank::ShankAccount; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, +}; pub use utils::*; use borsh::{BorshDeserialize, BorshSerialize}; -use crate::state::{Authority, DataBlob, Key, SolanaAccount}; +use crate::{ + error::MplAssetError, + state::{Authority, DataBlob, Key, SolanaAccount}, +}; #[repr(u16)] #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] pub enum Plugin { Reserved, - Royalties, + Royalties(Royalties), Delegate(Delegate), } +#[repr(u16)] +#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)] +pub enum PluginType { + Reserved, + Royalties, + Delegate, +} + +impl Plugin { + pub fn load(account: &AccountInfo, offset: usize) -> Result { + let mut bytes: &[u8] = &(*account.data).borrow()[offset..]; + Self::deserialize(&mut bytes).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::DeserializationError.into() + }) + } + + pub fn save(&self, account: &AccountInfo, offset: usize) -> ProgramResult { + borsh::to_writer(&mut account.data.borrow_mut()[offset..], self).map_err(|error| { + msg!("Error: {}", error); + MplAssetError::SerializationError.into() + }) + } +} + #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct RegistryData { @@ -30,7 +61,7 @@ pub struct RegistryData { #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct RegistryRecord { - pub key: Key, + pub plugin_type: PluginType, pub data: RegistryData, } diff --git a/programs/mpl-asset/src/plugins/royalties.rs b/programs/mpl-asset/src/plugins/royalties.rs index b94375f9..10e5d90b 100644 --- a/programs/mpl-asset/src/plugins/royalties.rs +++ b/programs/mpl-asset/src/plugins/royalties.rs @@ -1,13 +1,13 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub struct Creator { address: Pubkey, verified: bool, } -#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct Royalties { creators: Vec, auth_rules: Pubkey, diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index f5ed1fe4..562203ec 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -2,6 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::resize_or_reallocate_account_raw; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + program_memory::sol_memcpy, }; use crate::{ @@ -9,7 +10,7 @@ use crate::{ state::{Asset, Authority, DataBlob, Key, PluginHeader, SolanaAccount}, }; -use super::{Plugin, PluginRegistry, RegistryData, RegistryRecord}; +use super::{Plugin, PluginRegistry, PluginType, RegistryData, RegistryRecord}; /// Create plugin header and registry if it doesn't exist pub fn create_meta_idempotent<'a>( @@ -59,7 +60,7 @@ pub fn assert_plugins_initialized(account: &AccountInfo) -> ProgramResult { /// Fetch the plugin from the registry. pub fn fetch_plugin( account: &AccountInfo, - key: Key, + plugin_type: PluginType, ) -> Result<(Vec, Plugin, usize), ProgramError> { let mut bytes: &[u8] = &(*account.data).borrow(); let asset = Asset::deserialize(&mut bytes)?; @@ -68,27 +69,36 @@ pub fn fetch_plugin( let PluginRegistry { registry, .. } = PluginRegistry::load(account, header.plugin_registry_offset)?; + solana_program::msg!("{:?}", registry); + // Find the plugin in the registry. let plugin_data = registry .iter() .find( |RegistryRecord { - key: plugin_key, + plugin_type: plugin_type_iter, data: _, - }| *plugin_key == key, + }| *plugin_type_iter == plugin_type, + ) + .map( + |RegistryRecord { + plugin_type: _, + data, + }| data, ) - .map(|RegistryRecord { key: _, data }| data) .ok_or(MplAssetError::PluginNotFound)?; + solana_program::msg!("Deserialize plugin at offset"); // Deserialize the plugin. let plugin = Plugin::deserialize(&mut &(*account.data).borrow()[plugin_data.offset..])?; + solana_program::msg!("Return plugin"); // Return the plugin and its authorities. Ok((plugin_data.authorities.clone(), plugin, plugin_data.offset)) } /// Create plugin header and registry if it doesn't exist -pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { +pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { let mut bytes: &[u8] = &(*account.data).borrow(); let asset = Asset::deserialize(&mut bytes)?; @@ -98,7 +108,7 @@ pub fn list_plugins(account: &AccountInfo) -> Result, ProgramError> { Ok(registry .iter() - .map(|registry_record| registry_record.key) + .map(|registry_record| registry_record.plugin_type) .collect()) } @@ -119,21 +129,23 @@ pub fn add_plugin_or_authority<'a>( let mut header = PluginHeader::load(account, asset.get_size())?; let mut plugin_registry = PluginRegistry::load(account, header.plugin_registry_offset)?; - let (plugin_size, key) = match plugin { + let plugin_type = match plugin { Plugin::Reserved => return Err(MplAssetError::InvalidPlugin.into()), - Plugin::Royalties => todo!(), - Plugin::Delegate(delegate) => (delegate.get_size(), Key::Delegate), + Plugin::Royalties(_) => PluginType::Royalties, + Plugin::Delegate(_) => PluginType::Delegate, }; + let plugin_data = plugin.try_to_vec()?; + let plugin_size = plugin_data.len(); let authority_bytes = authority.try_to_vec()?; if let Some(RegistryRecord { - key: _, + plugin_type: _, data: registry_data, }) = plugin_registry.registry.iter_mut().find( |RegistryRecord { - key: search_key, + plugin_type: type_iter, data: _, - }| search_key == &key, + }| type_iter == &plugin_type, ) { registry_data.authorities.push(authority.clone()); @@ -166,7 +178,7 @@ pub fn add_plugin_or_authority<'a>( header.plugin_registry_offset = new_registry_offset; plugin_registry.registry.push(RegistryRecord { - key, + plugin_type, data: registry_data.clone(), }); @@ -178,12 +190,7 @@ pub fn add_plugin_or_authority<'a>( resize_or_reallocate_account_raw(account, payer, system_program, new_size)?; header.save(account, asset.get_size())?; - match plugin { - Plugin::Reserved => return Err(MplAssetError::InvalidPlugin.into()), - Plugin::Royalties => todo!(), - Plugin::Delegate(delegate) => delegate.save(account, old_registry_offset)?, - }; - + plugin.save(account, old_registry_offset)?; plugin_registry.save(account, new_registry_offset)?; } diff --git a/programs/mpl-asset/src/processor/freeze.rs b/programs/mpl-asset/src/processor/freeze.rs index f868a591..872c8eee 100644 --- a/programs/mpl-asset/src/processor/freeze.rs +++ b/programs/mpl-asset/src/processor/freeze.rs @@ -7,8 +7,7 @@ use solana_program::{ use crate::{ error::MplAssetError, instruction::accounts::FreezeAccounts, - plugins::{fetch_plugin, Delegate, Plugin}, - state::{Key, SolanaAccount}, + plugins::{fetch_plugin, Delegate, Plugin, PluginType}, utils::assert_authority, }; @@ -22,7 +21,7 @@ pub(crate) fn freeze<'a>(accounts: &'a [AccountInfo<'a>], _args: FreezeArgs) -> assert_signer(ctx.accounts.delegate)?; let (authorities, mut plugin, offset) = - fetch_plugin(ctx.accounts.asset_address, Key::Delegate)?; + fetch_plugin(ctx.accounts.asset_address, PluginType::Delegate)?; assert_authority( ctx.accounts.asset_address, @@ -38,7 +37,7 @@ pub(crate) fn freeze<'a>(accounts: &'a [AccountInfo<'a>], _args: FreezeArgs) -> _ => Err(MplAssetError::InvalidPlugin.into()), }?; - delegate.save(ctx.accounts.asset_address, offset)?; + Plugin::Delegate(*delegate).save(ctx.accounts.asset_address, offset)?; Ok(()) } diff --git a/programs/mpl-asset/src/processor/thaw.rs b/programs/mpl-asset/src/processor/thaw.rs index a39f76ea..3ca6ffd0 100644 --- a/programs/mpl-asset/src/processor/thaw.rs +++ b/programs/mpl-asset/src/processor/thaw.rs @@ -7,8 +7,7 @@ use solana_program::{ use crate::{ error::MplAssetError, instruction::accounts::ThawAccounts, - plugins::{fetch_plugin, Delegate, Plugin}, - state::{Key, SolanaAccount}, + plugins::{fetch_plugin, Delegate, Plugin, PluginType}, utils::assert_authority, }; @@ -22,7 +21,7 @@ pub(crate) fn thaw<'a>(accounts: &'a [AccountInfo<'a>], _args: ThawArgs) -> Prog assert_signer(ctx.accounts.delegate)?; let (authorities, mut plugin, offset) = - fetch_plugin(ctx.accounts.asset_address, Key::Delegate)?; + fetch_plugin(ctx.accounts.asset_address, PluginType::Delegate)?; assert_authority( ctx.accounts.asset_address, @@ -38,7 +37,7 @@ pub(crate) fn thaw<'a>(accounts: &'a [AccountInfo<'a>], _args: ThawArgs) -> Prog _ => Err(MplAssetError::InvalidPlugin.into()), }?; - delegate.save(ctx.accounts.asset_address, offset)?; + Plugin::Delegate(*delegate).save(ctx.accounts.asset_address, offset)?; Ok(()) } diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index 24bc72c8..d6671b68 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -5,7 +5,7 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplAssetError, instruction::accounts::TransferAccounts, - plugins::{fetch_plugin, Plugin}, + plugins::{fetch_plugin, Plugin, PluginType}, state::{Asset, Compressible, CompressionProof, DataBlob, HashedAsset, Key, SolanaAccount}, utils::{assert_authority, load_key}, }; @@ -44,9 +44,11 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) let mut authority_check: Result<(), ProgramError> = Err(MplAssetError::InvalidAuthority.into()); if asset.get_size() != ctx.accounts.asset_address.data_len() { + solana_program::msg!("Fetch Plugin"); let (authorities, plugin, _) = - fetch_plugin(ctx.accounts.asset_address, Key::Delegate)?; + fetch_plugin(ctx.accounts.asset_address, PluginType::Delegate)?; + solana_program::msg!("Assert authority"); authority_check = assert_authority( ctx.accounts.asset_address, ctx.accounts.authority, diff --git a/programs/mpl-asset/src/state/mod.rs b/programs/mpl-asset/src/state/mod.rs index 756f67ad..c93af589 100644 --- a/programs/mpl-asset/src/state/mod.rs +++ b/programs/mpl-asset/src/state/mod.rs @@ -12,7 +12,6 @@ mod traits; pub use traits::*; use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use crate::plugins::Plugin; @@ -52,11 +51,8 @@ pub enum Key { Uninitialized, Asset, HashedAsset, - Collection, - HashedCollection, PluginHeader, PluginRegistry, - Delegate, } impl Key { From 9d2bc5ec23b197e343121adf678df04a1612e5a7 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Sun, 18 Feb 2024 21:47:41 -0700 Subject: [PATCH 18/20] Fixing tests for delegate. --- README.md | 5 -- clients/js/package.json | 4 +- clients/js/test/delegate.test.ts | 133 +++++++++++++++++++++++++++++++ clients/js/test/info.test.ts | 10 +-- 4 files changed, 140 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b456a533..31a06d88 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,3 @@ This project contains the following clients: ## Contributing Check out the [Contributing Guide](./CONTRIBUTING.md) the learn more about how to contribute to this project. - -TODO: -Today: Functional Asset - utilities, transfer, burn + JS tests, stretch goal compress/decompress -Tomorrow: Migrate, Plugins (Collection and Delgate which contains Freeze) + JS tests, compress/decompress, royalties -Next day: Lifecycle hooks with Pick 3 PDAs + JS tests diff --git a/clients/js/package.json b/clients/js/package.json index 71baec67..84546333 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "rimraf dist && tsc -p tsconfig.json", "build:docs": "typedoc", - "test": "ava", + "test": "ava --timeout 600s", "lint": "eslint --ext js,ts,tsx src", "lint:fix": "eslint --fix --ext js,ts,tsx src", "format": "prettier --check src test", @@ -61,4 +61,4 @@ } }, "packageManager": "pnpm@8.2.0" -} +} \ No newline at end of file diff --git a/clients/js/test/delegate.test.ts b/clients/js/test/delegate.test.ts index 1eb03d1f..5cfb3127 100644 --- a/clients/js/test/delegate.test.ts +++ b/clients/js/test/delegate.test.ts @@ -18,6 +18,9 @@ import { getPluginHeaderAccountDataSerializer, getPluginRegistryAccountDataSerializer, PluginType, + freeze, + getPluginSerializer, + Plugin, } from '../src'; import { createUmi } from './_setup'; @@ -78,3 +81,133 @@ test('it can delegate a new authority', async (t) => { t.pass(); }); + +test('it can delegate several new authorities', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const delegateAddress0 = generateSigner(umi); + const delegateAddress1 = 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); + + // Then an account was created with the correct data. + const asset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Before Plugins:\n", asset); + const assetData = getAssetAccountDataSerializer().serialize(asset); + + await delegate(umi, { + assetAddress: assetAddress.publicKey, + owner: umi.identity, + delegate: delegateAddress0.publicKey, + }).sendAndConfirm(umi); + + await delegate(umi, { + assetAddress: assetAddress.publicKey, + owner: umi.identity, + delegate: delegateAddress1.publicKey, + }).sendAndConfirm(umi); + + const pluginData = await umi.rpc.getAccount(assetAddress.publicKey); + if (pluginData.exists) { + const pluginHeader = getPluginHeaderAccountDataSerializer().deserialize(pluginData.data, assetData.length)[0]; + // console.log("After Plugins:\n", pluginHeader); + t.like(pluginHeader, { + key: Key.PluginHeader, + pluginRegistryOffset: BigInt(119), + }); + const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(pluginData.data, Number(pluginHeader.pluginRegistryOffset))[0]; + // console.log(JSON.stringify(pluginRegistry, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + t.like(pluginRegistry, { + key: Key.PluginRegistry, + registry: [{ + pluginType: PluginType.Delegate, + data: { + offset: BigInt(117), + authorities: [{ __kind: 'Pubkey', address: delegateAddress0.publicKey }, { __kind: 'Pubkey', address: delegateAddress1.publicKey }], + }, + }], + externalPlugins: [], + }); + const delegatePlugin = getDelegateSerializer().deserialize(pluginData.data, Number(pluginRegistry.registry[0].data.offset))[0]; + // console.log(delegatePlugin); + t.like(delegatePlugin, { + frozen: false, + }); + } else { + t.fail("Plugin data not found"); + } + + t.pass(); +}); + +test('a delegate can freeze the token', async (t) => { + // Given a Umi instance and a new signer. + const umi = await createUmi(); + const assetAddress = generateSigner(umi); + const delegateAddress = 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); + + // Then an account was created with the correct data. + const asset = await fetchAsset(umi, assetAddress.publicKey); + // console.log("Before Plugins:\n", asset); + const assetData = getAssetAccountDataSerializer().serialize(asset); + + await delegate(umi, { + assetAddress: assetAddress.publicKey, + owner: umi.identity, + delegate: delegateAddress.publicKey, + }) + .append(freeze(umi, { + assetAddress: assetAddress.publicKey, + delegate: delegateAddress + })) + .sendAndConfirm(umi); + + const pluginData = await umi.rpc.getAccount(assetAddress.publicKey); + if (pluginData.exists) { + // console.log("Plugin Data:\n", pluginData.data); + const pluginHeader = getPluginHeaderAccountDataSerializer().deserialize(pluginData.data, assetData.length)[0]; + // console.log("After Plugins:\n", pluginHeader); + t.like(pluginHeader, { + key: Key.PluginHeader, + pluginRegistryOffset: BigInt(119), + }); + const pluginRegistry = getPluginRegistryAccountDataSerializer().deserialize(pluginData.data, Number(pluginHeader.pluginRegistryOffset))[0]; + // console.log(JSON.stringify(pluginRegistry, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + t.like(pluginRegistry, { + key: Key.PluginRegistry, + registry: [{ + pluginType: PluginType.Delegate, + data: { + offset: BigInt(117), + authorities: [{ __kind: 'Pubkey', address: delegateAddress.publicKey }], + }, + }], + externalPlugins: [], + }); + const delegatePlugin = getPluginSerializer().deserialize(pluginData.data, Number(pluginRegistry.registry[0].data.offset))[0]; + // console.log(delegatePlugin); + t.like(delegatePlugin, { + __kind: 'Delegate', + fields: [{ frozen: true }] + }); + } else { + t.fail("Plugin data not found"); + } + + t.pass(); +}); \ No newline at end of file diff --git a/clients/js/test/info.test.ts b/clients/js/test/info.test.ts index 09511cd7..d27c9991 100644 --- a/clients/js/test/info.test.ts +++ b/clients/js/test/info.test.ts @@ -1,6 +1,6 @@ import { generateSigner, publicKey } from '@metaplex-foundation/umi'; import test from 'ava'; -import { DataState, create, fetchAsset, fetchHashedAsset } from '../src'; +import { DataState, create, /* fetchAsset, fetchHashedAsset */ } from '../src'; import { createUmi } from './_setup'; test('fetch account info for account state', async (t) => { @@ -23,8 +23,8 @@ test('fetch account info for account state', async (t) => { } // Then an account was created with the correct data. - const nonFungible = await fetchAsset(umi, assetAddress.publicKey); - console.log(nonFungible); + // const nonFungible = await fetchAsset(umi, assetAddress.publicKey); + // console.log(nonFungible); t.pass(); }); @@ -50,8 +50,8 @@ test('fetch account info for ledger state', async (t) => { } // Then an account was created with the correct data. - const nonFungible = await fetchHashedAsset(umi, assetAddress.publicKey); - console.log(nonFungible); + // const nonFungible = await fetchHashedAsset(umi, assetAddress.publicKey); + // console.log(nonFungible); t.pass(); }); From 36a8b8e44e1dc89bb537d88d3e9851ff2a5fbfdb Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Mon, 19 Feb 2024 13:13:57 -0700 Subject: [PATCH 19/20] Minor tweaks. --- Cargo.lock | 23 +++++ Cargo.toml | 5 +- clients/js/src/generated/errors/mplAsset.ts | 29 ++++++ .../js/src/generated/instructions/migrate.ts | 2 +- clients/js/src/generated/types/assetSigner.ts | 23 +++++ clients/js/src/generated/types/index.ts | 3 + .../js/src/generated/types/legacyMetadata.ts | 27 ++++++ clients/js/src/generated/types/plugin.ts | 34 ++++++- clients/js/src/generated/types/pluginType.ts | 2 + clients/js/src/generated/types/royalties.ts | 23 +++-- clients/js/src/generated/types/ruleSet.ts | 69 ++++++++++++++ .../rust/src/generated/errors/mpl_asset.rs | 6 ++ .../src/generated/instructions/migrate.rs | 53 ++++------- .../rust/src/generated/types/asset_signer.rs | 13 +++ .../src/generated/types/legacy_metadata.rs | 20 +++++ clients/rust/src/generated/types/mod.rs | 6 ++ clients/rust/src/generated/types/plugin.rs | 4 + .../rust/src/generated/types/plugin_type.rs | 2 + clients/rust/src/generated/types/royalties.rs | 10 +-- clients/rust/src/generated/types/rule_set.rs | 17 ++++ idls/mpl_asset_program.json | 90 +++++++++++++++++-- programs/mpl-asset/Cargo.toml | 1 + programs/mpl-asset/src/error.rs | 8 ++ programs/mpl-asset/src/instruction.rs | 6 +- .../mpl-asset/src/plugins/asset_signer.rs | 17 ++++ .../mpl-asset/src/plugins/legacy_metadata.rs | 20 +++++ programs/mpl-asset/src/plugins/mod.rs | 22 +++++ programs/mpl-asset/src/plugins/royalties.rs | 10 ++- programs/mpl-asset/src/plugins/utils.rs | 7 +- programs/mpl-asset/src/processor/migrate.rs | 44 +++++++-- 30 files changed, 513 insertions(+), 83 deletions(-) create mode 100644 clients/js/src/generated/types/assetSigner.ts create mode 100644 clients/js/src/generated/types/legacyMetadata.ts create mode 100644 clients/js/src/generated/types/ruleSet.ts create mode 100644 clients/rust/src/generated/types/asset_signer.rs create mode 100644 clients/rust/src/generated/types/legacy_metadata.rs create mode 100644 clients/rust/src/generated/types/rule_set.rs create mode 100644 programs/mpl-asset/src/plugins/asset_signer.rs create mode 100644 programs/mpl-asset/src/plugins/legacy_metadata.rs diff --git a/Cargo.lock b/Cargo.lock index 0172de5b..ae1d7741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1554,6 +1554,15 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "hashed-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.76", + "quote 1.0.35", + "syn 1.0.109", +] + [[package]] name = "heck" version = "0.4.1" @@ -2094,6 +2103,7 @@ version = "0.1.0" dependencies = [ "borsh 0.10.3", "bytemuck", + "mpl-token-metadata", "mpl-utils", "num-derive 0.3.3", "num-traits", @@ -2103,6 +2113,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mpl-token-metadata" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b2de608098eb2ef2a5392069dea83084967e25a4d69d0380a6bb02454fc0fe" +dependencies = [ + "borsh 0.10.3", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "mpl-utils" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index b027c3b6..ca94b605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,3 @@ [workspace] resolver = "2" -members = [ - "clients/rust", - "programs/mpl-asset" -] +members = ["clients/rust", "programs/mpl-asset"] diff --git a/clients/js/src/generated/errors/mplAsset.ts b/clients/js/src/generated/errors/mplAsset.ts index a34dc744..1660f465 100644 --- a/clients/js/src/generated/errors/mplAsset.ts +++ b/clients/js/src/generated/errors/mplAsset.ts @@ -171,6 +171,35 @@ export class MissingCompressionProofError extends ProgramError { codeToErrorMap.set(0xb, MissingCompressionProofError); nameToErrorMap.set('MissingCompressionProof', MissingCompressionProofError); +/** CannotMigrateMasterWithSupply: Cannot migrate a master edition used for prints */ +export class CannotMigrateMasterWithSupplyError extends ProgramError { + readonly name: string = 'CannotMigrateMasterWithSupply'; + + readonly code: number = 0xc; // 12 + + constructor(program: Program, cause?: Error) { + super('Cannot migrate a master edition used for prints', program, cause); + } +} +codeToErrorMap.set(0xc, CannotMigrateMasterWithSupplyError); +nameToErrorMap.set( + 'CannotMigrateMasterWithSupply', + CannotMigrateMasterWithSupplyError +); + +/** CannotMigratePrints: Cannot migrate a print edition */ +export class CannotMigratePrintsError extends ProgramError { + readonly name: string = 'CannotMigratePrints'; + + readonly code: number = 0xd; // 13 + + constructor(program: Program, cause?: Error) { + super('Cannot migrate a print edition', program, cause); + } +} +codeToErrorMap.set(0xd, CannotMigratePrintsError); +nameToErrorMap.set('CannotMigratePrints', CannotMigratePrintsError); + /** * Attempts to resolve a custom program error from the provided error code. * @category Errors diff --git a/clients/js/src/generated/instructions/migrate.ts b/clients/js/src/generated/instructions/migrate.ts index d3e2d070..44673a9f 100644 --- a/clients/js/src/generated/instructions/migrate.ts +++ b/clients/js/src/generated/instructions/migrate.ts @@ -51,7 +51,7 @@ export type MigrateInstructionAccounts = { /** Metadata (pda of ['metadata', program id, mint id]) */ metadata: PublicKey | Pda; /** Edition of token asset */ - edition?: PublicKey | Pda; + edition: PublicKey | Pda; /** Owner token record account */ ownerTokenRecord?: PublicKey | Pda; /** SPL Token Program */ diff --git a/clients/js/src/generated/types/assetSigner.ts b/clients/js/src/generated/types/assetSigner.ts new file mode 100644 index 00000000..91071730 --- /dev/null +++ b/clients/js/src/generated/types/assetSigner.ts @@ -0,0 +1,23 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { Serializer, struct } from '@metaplex-foundation/umi/serializers'; + +export type AssetSigner = {}; + +export type AssetSignerArgs = AssetSigner; + +export function getAssetSignerSerializer(): Serializer< + AssetSignerArgs, + AssetSigner +> { + return struct([], { description: 'AssetSigner' }) as Serializer< + AssetSignerArgs, + AssetSigner + >; +} diff --git a/clients/js/src/generated/types/index.ts b/clients/js/src/generated/types/index.ts index f0983d70..55e142c4 100644 --- a/clients/js/src/generated/types/index.ts +++ b/clients/js/src/generated/types/index.ts @@ -6,6 +6,7 @@ * @see https://github.com/metaplex-foundation/kinobi */ +export * from './assetSigner'; export * from './authority'; export * from './collection'; export * from './compressionProof'; @@ -15,9 +16,11 @@ export * from './delegate'; export * from './externalPluginRecord'; export * from './extraAccounts'; export * from './key'; +export * from './legacyMetadata'; export * from './migrationLevel'; export * from './plugin'; export * from './pluginType'; export * from './registryData'; export * from './registryRecord'; export * from './royalties'; +export * from './ruleSet'; diff --git a/clients/js/src/generated/types/legacyMetadata.ts b/clients/js/src/generated/types/legacyMetadata.ts new file mode 100644 index 00000000..154c142e --- /dev/null +++ b/clients/js/src/generated/types/legacyMetadata.ts @@ -0,0 +1,27 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + Serializer, + publicKey as publicKeySerializer, + struct, +} from '@metaplex-foundation/umi/serializers'; + +export type LegacyMetadata = { mint: PublicKey }; + +export type LegacyMetadataArgs = LegacyMetadata; + +export function getLegacyMetadataSerializer(): Serializer< + LegacyMetadataArgs, + LegacyMetadata +> { + return struct([['mint', publicKeySerializer()]], { + description: 'LegacyMetadata', + }) as Serializer; +} diff --git a/clients/js/src/generated/types/plugin.ts b/clients/js/src/generated/types/plugin.ts index e10faa68..aea54d7a 100644 --- a/clients/js/src/generated/types/plugin.ts +++ b/clients/js/src/generated/types/plugin.ts @@ -16,23 +16,33 @@ import { unit, } from '@metaplex-foundation/umi/serializers'; import { + AssetSigner, + AssetSignerArgs, Delegate, DelegateArgs, + LegacyMetadata, + LegacyMetadataArgs, Royalties, RoyaltiesArgs, + getAssetSignerSerializer, getDelegateSerializer, + getLegacyMetadataSerializer, getRoyaltiesSerializer, } from '.'; export type Plugin = | { __kind: 'Reserved' } | { __kind: 'Royalties'; fields: [Royalties] } - | { __kind: 'Delegate'; fields: [Delegate] }; + | { __kind: 'Delegate'; fields: [Delegate] } + | { __kind: 'LegacyMetadata'; fields: [LegacyMetadata] } + | { __kind: 'AssetSigner'; fields: [AssetSigner] }; export type PluginArgs = | { __kind: 'Reserved' } | { __kind: 'Royalties'; fields: [RoyaltiesArgs] } - | { __kind: 'Delegate'; fields: [DelegateArgs] }; + | { __kind: 'Delegate'; fields: [DelegateArgs] } + | { __kind: 'LegacyMetadata'; fields: [LegacyMetadataArgs] } + | { __kind: 'AssetSigner'; fields: [AssetSignerArgs] }; export function getPluginSerializer(): Serializer { return dataEnum( @@ -50,6 +60,18 @@ export function getPluginSerializer(): Serializer { ['fields', tuple([getDelegateSerializer()])], ]), ], + [ + 'LegacyMetadata', + struct>([ + ['fields', tuple([getLegacyMetadataSerializer()])], + ]), + ], + [ + 'AssetSigner', + struct>([ + ['fields', tuple([getAssetSignerSerializer()])], + ]), + ], ], { description: 'Plugin' } ) as Serializer; @@ -67,6 +89,14 @@ export function plugin( kind: 'Delegate', data: GetDataEnumKindContent['fields'] ): GetDataEnumKind; +export function plugin( + kind: 'LegacyMetadata', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function plugin( + kind: 'AssetSigner', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; export function plugin( kind: K, data?: any diff --git a/clients/js/src/generated/types/pluginType.ts b/clients/js/src/generated/types/pluginType.ts index 9948bd21..c8baa0ca 100644 --- a/clients/js/src/generated/types/pluginType.ts +++ b/clients/js/src/generated/types/pluginType.ts @@ -12,6 +12,8 @@ export enum PluginType { Reserved, Royalties, Delegate, + LegacyMetadata, + AssetSigner, } export type PluginTypeArgs = PluginType; diff --git a/clients/js/src/generated/types/royalties.ts b/clients/js/src/generated/types/royalties.ts index 77382e10..8698fd55 100644 --- a/clients/js/src/generated/types/royalties.ts +++ b/clients/js/src/generated/types/royalties.ts @@ -6,34 +6,39 @@ * @see https://github.com/metaplex-foundation/kinobi */ -import { PublicKey } from '@metaplex-foundation/umi'; import { Serializer, array, - publicKey as publicKeySerializer, struct, u16, } from '@metaplex-foundation/umi/serializers'; -import { Creator, CreatorArgs, getCreatorSerializer } from '.'; +import { + Creator, + CreatorArgs, + RuleSet, + RuleSetArgs, + getCreatorSerializer, + getRuleSetSerializer, +} from '.'; export type Royalties = { - creators: Array; - authRules: PublicKey; sellerFeeBasisPoints: number; + creators: Array; + ruleSet: RuleSet; }; export type RoyaltiesArgs = { - creators: Array; - authRules: PublicKey; sellerFeeBasisPoints: number; + creators: Array; + ruleSet: RuleSetArgs; }; export function getRoyaltiesSerializer(): Serializer { return struct( [ - ['creators', array(getCreatorSerializer())], - ['authRules', publicKeySerializer()], ['sellerFeeBasisPoints', u16()], + ['creators', array(getCreatorSerializer())], + ['ruleSet', getRuleSetSerializer()], ], { description: 'Royalties' } ) as Serializer; diff --git a/clients/js/src/generated/types/ruleSet.ts b/clients/js/src/generated/types/ruleSet.ts new file mode 100644 index 00000000..33c76ced --- /dev/null +++ b/clients/js/src/generated/types/ruleSet.ts @@ -0,0 +1,69 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/metaplex-foundation/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi'; +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + array, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, +} from '@metaplex-foundation/umi/serializers'; + +export type RuleSet = + | { __kind: 'ProgramAllowList'; fields: [Array] } + | { __kind: 'ProgramDenyList'; fields: [Array] }; + +export type RuleSetArgs = RuleSet; + +export function getRuleSetSerializer(): Serializer { + return dataEnum( + [ + [ + 'ProgramAllowList', + struct>([ + ['fields', tuple([array(publicKeySerializer())])], + ]), + ], + [ + 'ProgramDenyList', + struct>([ + ['fields', tuple([array(publicKeySerializer())])], + ]), + ], + ], + { description: 'RuleSet' } + ) as Serializer; +} + +// Data Enum Helpers. +export function ruleSet( + kind: 'ProgramAllowList', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function ruleSet( + kind: 'ProgramDenyList', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind; +export function ruleSet( + kind: K, + data?: any +): Extract { + return Array.isArray(data) + ? { __kind: kind, fields: data } + : { __kind: kind, ...(data ?? {}) }; +} +export function isRuleSet( + kind: K, + value: RuleSet +): value is RuleSet & { __kind: K } { + return value.__kind === kind; +} diff --git a/clients/rust/src/generated/errors/mpl_asset.rs b/clients/rust/src/generated/errors/mpl_asset.rs index d1019602..97d4749d 100644 --- a/clients/rust/src/generated/errors/mpl_asset.rs +++ b/clients/rust/src/generated/errors/mpl_asset.rs @@ -46,6 +46,12 @@ pub enum MplAssetError { /// 11 (0xB) - Missing compression proof #[error("Missing compression proof")] MissingCompressionProof, + /// 12 (0xC) - Cannot migrate a master edition used for prints + #[error("Cannot migrate a master edition used for prints")] + CannotMigrateMasterWithSupply, + /// 13 (0xD) - Cannot migrate a print edition + #[error("Cannot migrate a print edition")] + CannotMigratePrints, } impl solana_program::program_error::PrintProgramError for MplAssetError { diff --git a/clients/rust/src/generated/instructions/migrate.rs b/clients/rust/src/generated/instructions/migrate.rs index ac3d3b84..c3fedab0 100644 --- a/clients/rust/src/generated/instructions/migrate.rs +++ b/clients/rust/src/generated/instructions/migrate.rs @@ -27,7 +27,7 @@ pub struct Migrate { /// Metadata (pda of ['metadata', program id, mint id]) pub metadata: solana_program::pubkey::Pubkey, /// Edition of token asset - pub edition: Option, + pub edition: solana_program::pubkey::Pubkey, /// Owner token record account pub owner_token_record: Option, /// SPL Token Program @@ -95,16 +95,10 @@ impl Migrate { self.metadata, false, )); - if let Some(edition) = self.edition { - accounts.push(solana_program::instruction::AccountMeta::new( - edition, false, - )); - } else { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - crate::MPL_ASSET_ID, - false, - )); - } + accounts.push(solana_program::instruction::AccountMeta::new( + self.edition, + false, + )); if let Some(owner_token_record) = self.owner_token_record { accounts.push(solana_program::instruction::AccountMeta::new( owner_token_record, @@ -263,11 +257,10 @@ impl MigrateBuilder { self.metadata = Some(metadata); self } - /// `[optional account]` /// Edition of token asset #[inline(always)] - pub fn edition(&mut self, edition: Option) -> &mut Self { - self.edition = edition; + pub fn edition(&mut self, edition: solana_program::pubkey::Pubkey) -> &mut Self { + self.edition = Some(edition); self } /// `[optional account]` @@ -375,7 +368,7 @@ impl MigrateBuilder { token: self.token.expect("token is not set"), mint: self.mint.expect("mint is not set"), metadata: self.metadata.expect("metadata is not set"), - edition: self.edition, + edition: self.edition.expect("edition is not set"), owner_token_record: self.owner_token_record, spl_token_program: self.spl_token_program.unwrap_or(solana_program::pubkey!( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" @@ -416,7 +409,7 @@ pub struct MigrateCpiAccounts<'a, 'b> { /// Metadata (pda of ['metadata', program id, mint id]) pub metadata: &'b solana_program::account_info::AccountInfo<'a>, /// Edition of token asset - pub edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + pub edition: &'b solana_program::account_info::AccountInfo<'a>, /// Owner token record account pub owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// SPL Token Program @@ -452,7 +445,7 @@ pub struct MigrateCpi<'a, 'b> { /// Metadata (pda of ['metadata', program id, mint id]) pub metadata: &'b solana_program::account_info::AccountInfo<'a>, /// Edition of token asset - pub edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + pub edition: &'b solana_program::account_info::AccountInfo<'a>, /// Owner token record account pub owner_token_record: Option<&'b solana_program::account_info::AccountInfo<'a>>, /// SPL Token Program @@ -572,17 +565,10 @@ impl<'a, 'b> MigrateCpi<'a, 'b> { *self.metadata.key, false, )); - if let Some(edition) = self.edition { - accounts.push(solana_program::instruction::AccountMeta::new( - *edition.key, - false, - )); - } else { - accounts.push(solana_program::instruction::AccountMeta::new_readonly( - crate::MPL_ASSET_ID, - false, - )); - } + accounts.push(solana_program::instruction::AccountMeta::new( + *self.edition.key, + false, + )); if let Some(owner_token_record) = self.owner_token_record { accounts.push(solana_program::instruction::AccountMeta::new( *owner_token_record.key, @@ -668,9 +654,7 @@ impl<'a, 'b> MigrateCpi<'a, 'b> { account_infos.push(self.token.clone()); account_infos.push(self.mint.clone()); account_infos.push(self.metadata.clone()); - if let Some(edition) = self.edition { - account_infos.push(edition.clone()); - } + account_infos.push(self.edition.clone()); if let Some(owner_token_record) = self.owner_token_record { account_infos.push(owner_token_record.clone()); } @@ -784,14 +768,13 @@ impl<'a, 'b> MigrateCpiBuilder<'a, 'b> { self.instruction.metadata = Some(metadata); self } - /// `[optional account]` /// Edition of token asset #[inline(always)] pub fn edition( &mut self, - edition: Option<&'b solana_program::account_info::AccountInfo<'a>>, + edition: &'b solana_program::account_info::AccountInfo<'a>, ) -> &mut Self { - self.instruction.edition = edition; + self.instruction.edition = Some(edition); self } /// `[optional account]` @@ -940,7 +923,7 @@ impl<'a, 'b> MigrateCpiBuilder<'a, 'b> { metadata: self.instruction.metadata.expect("metadata is not set"), - edition: self.instruction.edition, + edition: self.instruction.edition.expect("edition is not set"), owner_token_record: self.instruction.owner_token_record, diff --git a/clients/rust/src/generated/types/asset_signer.rs b/clients/rust/src/generated/types/asset_signer.rs new file mode 100644 index 00000000..6e0f43d5 --- /dev/null +++ b/clients/rust/src/generated/types/asset_signer.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AssetSigner {} diff --git a/clients/rust/src/generated/types/legacy_metadata.rs b/clients/rust/src/generated/types/legacy_metadata.rs new file mode 100644 index 00000000..62f38977 --- /dev/null +++ b/clients/rust/src/generated/types/legacy_metadata.rs @@ -0,0 +1,20 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct LegacyMetadata { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub mint: Pubkey, +} diff --git a/clients/rust/src/generated/types/mod.rs b/clients/rust/src/generated/types/mod.rs index d6d3d813..ee58c6cd 100644 --- a/clients/rust/src/generated/types/mod.rs +++ b/clients/rust/src/generated/types/mod.rs @@ -5,6 +5,7 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +pub(crate) mod asset_signer; pub(crate) mod authority; pub(crate) mod collection; pub(crate) mod compression_proof; @@ -14,13 +15,16 @@ pub(crate) mod delegate; pub(crate) mod external_plugin_record; pub(crate) mod extra_accounts; pub(crate) mod key; +pub(crate) mod legacy_metadata; pub(crate) mod migration_level; pub(crate) mod plugin; pub(crate) mod plugin_type; pub(crate) mod registry_data; pub(crate) mod registry_record; pub(crate) mod royalties; +pub(crate) mod rule_set; +pub use self::asset_signer::*; pub use self::authority::*; pub use self::collection::*; pub use self::compression_proof::*; @@ -30,9 +34,11 @@ pub use self::delegate::*; pub use self::external_plugin_record::*; pub use self::extra_accounts::*; pub use self::key::*; +pub use self::legacy_metadata::*; pub use self::migration_level::*; pub use self::plugin::*; pub use self::plugin_type::*; pub use self::registry_data::*; pub use self::registry_record::*; pub use self::royalties::*; +pub use self::rule_set::*; diff --git a/clients/rust/src/generated/types/plugin.rs b/clients/rust/src/generated/types/plugin.rs index 17f2d2e7..328a0c08 100644 --- a/clients/rust/src/generated/types/plugin.rs +++ b/clients/rust/src/generated/types/plugin.rs @@ -5,7 +5,9 @@ //! [https://github.com/metaplex-foundation/kinobi] //! +use crate::generated::types::AssetSigner; use crate::generated::types::Delegate; +use crate::generated::types::LegacyMetadata; use crate::generated::types::Royalties; use borsh::BorshDeserialize; use borsh::BorshSerialize; @@ -16,4 +18,6 @@ pub enum Plugin { Reserved, Royalties(Royalties), Delegate(Delegate), + LegacyMetadata(LegacyMetadata), + AssetSigner(AssetSigner), } diff --git a/clients/rust/src/generated/types/plugin_type.rs b/clients/rust/src/generated/types/plugin_type.rs index 5a846ec4..6decb21c 100644 --- a/clients/rust/src/generated/types/plugin_type.rs +++ b/clients/rust/src/generated/types/plugin_type.rs @@ -14,4 +14,6 @@ pub enum PluginType { Reserved, Royalties, Delegate, + LegacyMetadata, + AssetSigner, } diff --git a/clients/rust/src/generated/types/royalties.rs b/clients/rust/src/generated/types/royalties.rs index 55a3009b..b975007e 100644 --- a/clients/rust/src/generated/types/royalties.rs +++ b/clients/rust/src/generated/types/royalties.rs @@ -6,18 +6,14 @@ //! use crate::generated::types::Creator; +use crate::generated::types::RuleSet; use borsh::BorshDeserialize; use borsh::BorshSerialize; -use solana_program::pubkey::Pubkey; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Royalties { - pub creators: Vec, - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] - pub auth_rules: Pubkey, pub seller_fee_basis_points: u16, + pub creators: Vec, + pub rule_set: RuleSet, } diff --git a/clients/rust/src/generated/types/rule_set.rs b/clients/rust/src/generated/types/rule_set.rs new file mode 100644 index 00000000..d6acd201 --- /dev/null +++ b/clients/rust/src/generated/types/rule_set.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! [https://github.com/metaplex-foundation/kinobi] +//! + +use borsh::BorshDeserialize; +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum RuleSet { + ProgramAllowList(Vec), + ProgramDenyList(Vec), +} diff --git a/idls/mpl_asset_program.json b/idls/mpl_asset_program.json index 96e2ae5a..ffd7e39a 100644 --- a/idls/mpl_asset_program.json +++ b/idls/mpl_asset_program.json @@ -144,7 +144,6 @@ "name": "edition", "isMut": true, "isSigner": false, - "isOptional": true, "docs": [ "Edition of token asset" ] @@ -862,6 +861,13 @@ ] } }, + { + "name": "AssetSigner", + "type": { + "kind": "struct", + "fields": [] + } + }, { "name": "Collection", "type": { @@ -890,6 +896,18 @@ ] } }, + { + "name": "LegacyMetadata", + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "type": "publicKey" + } + ] + } + }, { "name": "Creator", "type": { @@ -911,6 +929,10 @@ "type": { "kind": "struct", "fields": [ + { + "name": "sellerFeeBasisPoints", + "type": "u16" + }, { "name": "creators", "type": { @@ -920,12 +942,10 @@ } }, { - "name": "authRules", - "type": "publicKey" - }, - { - "name": "sellerFeeBasisPoints", - "type": "u16" + "name": "ruleSet", + "type": { + "defined": "RuleSet" + } } ] } @@ -1099,6 +1119,22 @@ "defined": "Delegate" } ] + }, + { + "name": "LegacyMetadata", + "fields": [ + { + "defined": "LegacyMetadata" + } + ] + }, + { + "name": "AssetSigner", + "fields": [ + { + "defined": "AssetSigner" + } + ] } ] } @@ -1116,6 +1152,36 @@ }, { "name": "Delegate" + }, + { + "name": "LegacyMetadata" + }, + { + "name": "AssetSigner" + } + ] + } + }, + { + "name": "RuleSet", + "type": { + "kind": "enum", + "variants": [ + { + "name": "ProgramAllowList", + "fields": [ + { + "vec": "publicKey" + } + ] + }, + { + "name": "ProgramDenyList", + "fields": [ + { + "vec": "publicKey" + } + ] } ] } @@ -1318,6 +1384,16 @@ "code": 11, "name": "MissingCompressionProof", "msg": "Missing compression proof" + }, + { + "code": 12, + "name": "CannotMigrateMasterWithSupply", + "msg": "Cannot migrate a master edition used for prints" + }, + { + "code": 13, + "name": "CannotMigratePrints", + "msg": "Cannot migrate a print edition" } ], "metadata": { diff --git a/programs/mpl-asset/Cargo.toml b/programs/mpl-asset/Cargo.toml index 156bfc77..6c320c1a 100644 --- a/programs/mpl-asset/Cargo.toml +++ b/programs/mpl-asset/Cargo.toml @@ -19,3 +19,4 @@ thiserror = "^1.0" bytemuck = "1.14.1" mpl-utils = "0.3.3" spl-noop = { version = "0.2.0", features = ["cpi"] } +mpl-token-metadata = "4.1.1" diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index 36e72bd1..713ede34 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -51,6 +51,14 @@ pub enum MplAssetError { /// 11 - Missing compression proof #[error("Missing compression proof")] MissingCompressionProof, + + /// 12 - Cannot migrate a master edition used for prints + #[error("Cannot migrate a master edition used for prints")] + CannotMigrateMasterWithSupply, + + /// 13 - Cannot migrate a print edition + #[error("Cannot migrate a print edition")] + CannotMigratePrints, } impl PrintProgramError for MplAssetError { diff --git a/programs/mpl-asset/src/instruction.rs b/programs/mpl-asset/src/instruction.rs index 38e49791..fbc3a04a 100644 --- a/programs/mpl-asset/src/instruction.rs +++ b/programs/mpl-asset/src/instruction.rs @@ -21,6 +21,7 @@ pub enum MplAssetInstruction { Create(CreateArgs), //TODO: Implement this instruction + // keith WIP /// Migrate an mpl-token-metadata asset to an mpl-asset. #[account(0, writable, signer, name="asset_address", desc = "The address of the new asset")] #[account(1, optional, signer, name="owner", desc = "The authority of the new asset")] @@ -30,7 +31,7 @@ pub enum MplAssetInstruction { #[account(4, writable, name="token", desc="Token account")] #[account(5, writable, name="mint", desc="Mint of token asset")] #[account(6, writable, name="metadata", desc="Metadata (pda of ['metadata', program id, mint id])")] - #[account(7, optional, writable, name="edition", desc="Edition of token asset")] + #[account(7, writable, name="edition", desc="Edition of token asset")] #[account(8, optional, writable, name="owner_token_record", desc="Owner token record account")] #[account(9, name="spl_token_program", desc="SPL Token Program")] #[account(10, name="spl_ata_program", desc="SPL Associated Token Account program")] @@ -40,7 +41,6 @@ pub enum MplAssetInstruction { #[account(14, optional, name="authorization_rules", desc="Token Authorization Rules account")] Migrate(MigrateArgs), - //TODO: Implement this instruction /// Delegate an mpl-asset. #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, optional, name="collection", desc = "The collection to which the asset belongs")] @@ -80,14 +80,12 @@ pub enum MplAssetInstruction { #[account(5, optional, name="log_wrapper", desc = "The SPL Noop Program")] Update(UpdateArgs), - //TODO: Implement this instruction /// Freeze an mpl-asset. #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, signer, name="delegate", desc = "The delegate of the asset")] #[account(2, optional, name="log_wrapper", desc = "The SPL Noop Program")] Freeze(FreezeArgs), - //TODO: Implement this instruction /// Thaw an mpl-asset. #[account(0, writable, name="asset_address", desc = "The address of the asset")] #[account(1, signer, name="delegate", desc = "The delegate of the asset")] diff --git a/programs/mpl-asset/src/plugins/asset_signer.rs b/programs/mpl-asset/src/plugins/asset_signer.rs new file mode 100644 index 00000000..fca2783b --- /dev/null +++ b/programs/mpl-asset/src/plugins/asset_signer.rs @@ -0,0 +1,17 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::state::DataBlob; + +#[repr(C)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct AssetSigner {} + +impl DataBlob for AssetSigner { + fn get_initial_size() -> usize { + 0 + } + + fn get_size(&self) -> usize { + 0 + } +} diff --git a/programs/mpl-asset/src/plugins/legacy_metadata.rs b/programs/mpl-asset/src/plugins/legacy_metadata.rs new file mode 100644 index 00000000..20538f8d --- /dev/null +++ b/programs/mpl-asset/src/plugins/legacy_metadata.rs @@ -0,0 +1,20 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::state::DataBlob; + +#[repr(C)] +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub struct LegacyMetadata { + pub mint: Pubkey, // 32 +} + +impl DataBlob for LegacyMetadata { + fn get_initial_size() -> usize { + 32 + } + + fn get_size(&self) -> usize { + 32 + } +} diff --git a/programs/mpl-asset/src/plugins/mod.rs b/programs/mpl-asset/src/plugins/mod.rs index da436067..e80f5657 100644 --- a/programs/mpl-asset/src/plugins/mod.rs +++ b/programs/mpl-asset/src/plugins/mod.rs @@ -1,8 +1,14 @@ +mod asset_signer; mod collection; +mod delegate; +mod legacy_metadata; mod royalties; mod utils; +pub use asset_signer::*; pub use collection::*; +pub use delegate::*; +pub use legacy_metadata::*; pub use royalties::*; use shank::ShankAccount; @@ -24,6 +30,8 @@ pub enum Plugin { Reserved, Royalties(Royalties), Delegate(Delegate), + LegacyMetadata(LegacyMetadata), + AssetSigner(AssetSigner), } #[repr(u16)] @@ -32,6 +40,20 @@ pub enum PluginType { Reserved, Royalties, Delegate, + LegacyMetadata, + AssetSigner, +} + +impl From<&Plugin> for PluginType { + fn from(plugin: &Plugin) -> Self { + match plugin { + Plugin::Reserved => PluginType::Reserved, + Plugin::Royalties(_) => PluginType::Royalties, + Plugin::Delegate(_) => PluginType::Delegate, + Plugin::LegacyMetadata(_) => PluginType::LegacyMetadata, + Plugin::AssetSigner(_) => PluginType::AssetSigner, + } + } } impl Plugin { diff --git a/programs/mpl-asset/src/plugins/royalties.rs b/programs/mpl-asset/src/plugins/royalties.rs index 10e5d90b..5ae48a52 100644 --- a/programs/mpl-asset/src/plugins/royalties.rs +++ b/programs/mpl-asset/src/plugins/royalties.rs @@ -7,9 +7,15 @@ pub struct Creator { verified: bool, } +#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] +pub enum RuleSet { + ProgramAllowList(Vec), + ProgramDenyList(Vec), +} + #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct Royalties { - creators: Vec, - auth_rules: Pubkey, seller_fee_basis_points: u16, + creators: Vec, + rule_set: RuleSet, } diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index 562203ec..b6fa82da 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -2,7 +2,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::resize_or_reallocate_account_raw; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - program_memory::sol_memcpy, }; use crate::{ @@ -129,11 +128,7 @@ pub fn add_plugin_or_authority<'a>( let mut header = PluginHeader::load(account, asset.get_size())?; let mut plugin_registry = PluginRegistry::load(account, header.plugin_registry_offset)?; - let plugin_type = match plugin { - Plugin::Reserved => return Err(MplAssetError::InvalidPlugin.into()), - Plugin::Royalties(_) => PluginType::Royalties, - Plugin::Delegate(_) => PluginType::Delegate, - }; + let plugin_type = plugin.into(); let plugin_data = plugin.try_to_vec()?; let plugin_size = plugin_data.len(); let authority_bytes = authority.try_to_vec()?; diff --git a/programs/mpl-asset/src/processor/migrate.rs b/programs/mpl-asset/src/processor/migrate.rs index 9fc24dc1..3c656a9c 100644 --- a/programs/mpl-asset/src/processor/migrate.rs +++ b/programs/mpl-asset/src/processor/migrate.rs @@ -1,14 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use mpl_token_metadata::accounts::{MasterEdition, Metadata}; use mpl_utils::assert_signer; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_memory::sol_memcpy, rent::Rent, system_instruction, system_program, sysvar::Sysvar, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; use crate::{ error::MplAssetError, - instruction::accounts::CreateAccounts, - state::{Asset, Compressible, DataState, HashedAsset, Key, MigrationLevel}, + instruction::accounts::MigrateAccounts, + state::{DataState, MigrationLevel}, }; #[repr(C)] @@ -19,5 +17,39 @@ pub struct MigrateArgs { } pub(crate) fn migrate<'a>(accounts: &'a [AccountInfo<'a>], args: MigrateArgs) -> ProgramResult { + let ctx = MigrateAccounts::context(accounts)?; + + // Assert that the owner is the one performing the migration. + assert_signer(ctx.accounts.payer)?; + let authority = if let Some(owner) = ctx.accounts.owner { + assert_signer(owner)?; + owner + } else { + ctx.accounts.payer + }; + + let metadata = Metadata::safe_deserialize(&ctx.accounts.metadata.data.borrow())?; + + match metadata.collection_details { + // If this is a collection NFT then we need to initialize migration for the whole collection. + Some(_) => {} + // Otherwise, we need to migrate the NFT + None => { + // Assert that the NFT is not a print edition. + if ctx.accounts.edition.data.borrow()[1] + == mpl_token_metadata::types::Key::EditionV1 as u8 + { + return Err(MplAssetError::CannotMigratePrints.into()); + } + + // Assert that the NFT is not a master edition with a nonzero supply. + let master_edition = + MasterEdition::safe_deserialize(&ctx.accounts.edition.data.borrow())?; + if master_edition.max_supply != Some(0) { + return Err(MplAssetError::CannotMigrateMasterWithSupply.into()); + } + } + } + Ok(()) } From 89c9aa8869655398153eea03a46bb10c049e5cf6 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:00:21 -0800 Subject: [PATCH 20/20] Merge fixes --- clients/js/test/transfer.test.ts | 2 +- programs/mpl-asset/src/error.rs | 8 ++++++-- programs/mpl-asset/src/plugins/utils.rs | 8 ++++++-- programs/mpl-asset/src/processor/transfer.rs | 10 +++++++--- programs/mpl-asset/src/utils.rs | 9 ++++++--- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index 67f5e3ec..40802ca3 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -79,7 +79,7 @@ test('it cannot transfer an asset if not the owner', async (t) => { authority: attacker, }).sendAndConfirm(umi); - t.throwsAsync(result, { name: 'InvalidAuthority' }) + await t.throwsAsync(result, { name: 'InvalidAuthority' }) const afterAsset = await fetchAsset(umi, assetAddress.publicKey); // console.log("Account State:", afterAsset); diff --git a/programs/mpl-asset/src/error.rs b/programs/mpl-asset/src/error.rs index 713ede34..68f2fbaf 100644 --- a/programs/mpl-asset/src/error.rs +++ b/programs/mpl-asset/src/error.rs @@ -28,11 +28,15 @@ pub enum MplAssetError { #[error("Plugin not found")] PluginNotFound, - /// 5 - Incorrect account + /// 5 - Numerical Overflow + #[error("Numerical Overflow")] + NumericalOverflow, + + /// 6 - Incorrect account #[error("Incorrect account")] IncorrectAccount, - /// 5 - Provided data does not match asset hash. + /// 7 - Provided data does not match asset hash. #[error("Incorrect asset hash")] IncorrectAssetHash, diff --git a/programs/mpl-asset/src/plugins/utils.rs b/programs/mpl-asset/src/plugins/utils.rs index b6fa82da..84834fd4 100644 --- a/programs/mpl-asset/src/plugins/utils.rs +++ b/programs/mpl-asset/src/plugins/utils.rs @@ -29,7 +29,11 @@ pub fn create_meta_idempotent<'a>( key: Key::PluginHeader, plugin_registry_offset: asset.get_size() + PluginHeader::get_initial_size(), }; - let registry = PluginRegistry { registry: vec![] }; + let registry = PluginRegistry { + key: Key::PluginRegistry, + registry: vec![], + external_plugins: vec![], + }; resize_or_reallocate_account_raw( account, @@ -150,7 +154,7 @@ pub fn add_plugin_or_authority<'a>( .ok_or(MplAssetError::NumericalOverflow)?; resize_or_reallocate_account_raw(account, payer, system_program, new_size)?; - plugin_registry.save(account, header.plugin_registry_offset); + plugin_registry.save(account, header.plugin_registry_offset)?; } else { let old_registry_offset = header.plugin_registry_offset; let registry_data = RegistryData { diff --git a/programs/mpl-asset/src/processor/transfer.rs b/programs/mpl-asset/src/processor/transfer.rs index d6671b68..b90af136 100644 --- a/programs/mpl-asset/src/processor/transfer.rs +++ b/programs/mpl-asset/src/processor/transfer.rs @@ -1,6 +1,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use mpl_utils::assert_signer; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; use crate::{ error::MplAssetError, @@ -28,8 +30,10 @@ pub(crate) fn transfer<'a>(accounts: &'a [AccountInfo<'a>], args: TransferArgs) match load_key(ctx.accounts.asset_address, 0)? { Key::HashedAsset => { - let mut asset = - Asset::verify_proof(ctx.accounts.asset_address, args.compression_proof)?; + let compression_proof = args + .compression_proof + .ok_or(MplAssetError::MissingCompressionProof)?; + let mut asset = Asset::verify_proof(ctx.accounts.asset_address, compression_proof)?; asset.owner = *ctx.accounts.new_owner.key; diff --git a/programs/mpl-asset/src/utils.rs b/programs/mpl-asset/src/utils.rs index c4b0a4ed..dac78e63 100644 --- a/programs/mpl-asset/src/utils.rs +++ b/programs/mpl-asset/src/utils.rs @@ -1,8 +1,11 @@ -use crate::{error::MplAssetError, state::Key}; use num_traits::FromPrimitive; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke, - program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +use crate::{ + error::MplAssetError, + state::{Asset, Authority, Key, SolanaAccount}, }; pub fn load_key(account: &AccountInfo, offset: usize) -> Result {