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 236c9309..e91c38f0 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::*; @@ -62,3 +65,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 85979851..b4af3bb7 100644 --- a/programs/mpl-asset/src/state/plugin_header.rs +++ b/programs/mpl-asset/src/state/plugin_header.rs @@ -1,6 +1,5 @@ -use borsh::{BorshDeserialize, BorshSerialize}; - use crate::{state::Key, utils::DataBlob}; +use borsh::{BorshDeserialize, BorshSerialize}; #[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) +}