From 0ca98acaa9d35494e7667ca8a613c0e48ba45d6c Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Fri, 19 Apr 2024 17:53:17 -0700 Subject: [PATCH 1/9] init --- Anchor.toml | 3 +++ Cargo.lock | 21 +++++++++++++++++++ programs/mmm/Cargo.toml | 1 + programs/mmm/src/mpl-core.rs | 40 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 programs/mmm/src/mpl-core.rs diff --git a/Anchor.toml b/Anchor.toml index e74bd3e..ba681b2 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -36,6 +36,9 @@ address = "99jtJwGDfaBKXtc7kxQneAGbERGK8F5XyJWHv7qTbj9G" # global deny list for [[test.validator.clone]] address = "CZ1rQoAHSqWBoAEfqGsiLhgbM59dDrCWk3rnG5FXaoRV" # libreplex royalty enforcement +[[test.validator.clone]] +address = "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" # metaplex core program + [[test.genesis]] address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" program = "./tests/deps/spl_token_2022.so" diff --git a/Cargo.lock b/Cargo.lock index 0b283a1..3091c10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -420,6 +420,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "bincode" version = "1.3.3" @@ -1307,6 +1313,7 @@ dependencies = [ "anchor-spl", "community-managed-token", "m2_interface", + "mpl-core", "mpl-token-metadata 4.1.1", "open_creator_protocol", "solana-program", @@ -1317,6 +1324,20 @@ dependencies = [ "spl-token-metadata-interface", ] +[[package]] +name = "mpl-core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7477a52a59fe4db051c6a83077319288e8bfbd1aaf8511d4f733e3a77b6d24" +dependencies = [ + "base64 0.22.0", + "borsh 0.10.3", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "mpl-token-metadata" version = "3.2.3" diff --git a/programs/mmm/Cargo.toml b/programs/mmm/Cargo.toml index ac3ee6d..17d3c53 100644 --- a/programs/mmm/Cargo.toml +++ b/programs/mmm/Cargo.toml @@ -30,3 +30,4 @@ spl-associated-token-account = { version = "2.2.0", features = [ ] } spl-token-2022 = {version = "1.0.0", features = ["no-entrypoint"] } m2_interface = { path = "../m2_interface" } +mpl-core = "0.4.4" diff --git a/programs/mmm/src/mpl-core.rs b/programs/mmm/src/mpl-core.rs new file mode 100644 index 0000000..11a7998 --- /dev/null +++ b/programs/mmm/src/mpl-core.rs @@ -0,0 +1,40 @@ +use mpl_core::ID; +use solana_program::pubkey::Pubkey; +use std::ops::Deref; + +// TODO: move to shared library +#[derive(Clone)] +pub struct AssetInterface; + +impl anchor_lang::Ids for AssetInterface { + fn ids() -> &'static [Pubkey] { + &[ID] + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IndexableAsset(mpl_core::IndexableAsset); + +impl anchor_lang::AccountDeserialize for IndexableAsset { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + mpl_core::IndexableAsset::fetch(mpl_core::types::Key::AssetV1, buf) + .map(IndexableAsset) + .map_err(Into::into) + } +} + +impl anchor_lang::AccountSerialize for IndexableAsset {} + +impl anchor_lang::Owner for IndexableAsset { + fn owner() -> Pubkey { + ID + } +} + +impl Deref for IndexableAsset { + type Target = mpl_core::IndexableAsset; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} \ No newline at end of file From f9d2fe2bfe3dea1aaefa8b70034cb0e8bf257f6d Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Tue, 23 Apr 2024 16:47:22 -0700 Subject: [PATCH 2/9] deposit sell contract change --- programs/mmm/src/errors.rs | 2 + programs/mmm/src/instructions/mod.rs | 2 + .../src/instructions/mpl_core_asset/mod.rs | 7 ++ .../mpl_core_asset/mpl_core_deposit_sell.rs | 105 ++++++++++++++++ .../mpl_core_asset/mpl_core_wrap.rs} | 1 - programs/mmm/src/lib.rs | 7 ++ programs/mmm/src/state.rs | 2 + programs/mmm/src/util.rs | 58 +++++++++ sdk/src/idl/mmm.ts | 116 ++++++++++++++++++ 9 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 programs/mmm/src/instructions/mpl_core_asset/mod.rs create mode 100644 programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs rename programs/mmm/src/{mpl-core.rs => instructions/mpl_core_asset/mpl_core_wrap.rs} (96%) diff --git a/programs/mmm/src/errors.rs b/programs/mmm/src/errors.rs index f4dea86..e5a8fc8 100644 --- a/programs/mmm/src/errors.rs +++ b/programs/mmm/src/errors.rs @@ -66,4 +66,6 @@ pub enum MMMErrorCode { InvalidTokenMetadataExtension, // 0x178e #[msg("Invalid token member extensions")] InvalidTokenMemberExtension, // 0x178f + #[msg("Invalid asset collection")] + InvalidAssetCollection, } diff --git a/programs/mmm/src/instructions/mod.rs b/programs/mmm/src/instructions/mod.rs index 9c1b5a9..8201825 100644 --- a/programs/mmm/src/instructions/mod.rs +++ b/programs/mmm/src/instructions/mod.rs @@ -5,12 +5,14 @@ pub mod ext_vanilla; pub mod mip1; pub mod ocp; pub mod vanilla; +pub mod mpl_core_asset; pub use admin::*; pub use ext_vanilla::*; pub use mip1::*; pub use ocp::*; pub use vanilla::*; +pub use mpl_core_asset::*; use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; diff --git a/programs/mmm/src/instructions/mpl_core_asset/mod.rs b/programs/mmm/src/instructions/mpl_core_asset/mod.rs new file mode 100644 index 0000000..f6d59c8 --- /dev/null +++ b/programs/mmm/src/instructions/mpl_core_asset/mod.rs @@ -0,0 +1,7 @@ +#![allow(missing_docs)] + +pub mod mpl_core_wrap; +pub mod mpl_core_deposit_sell; + +pub use mpl_core_wrap::*; +pub use mpl_core_deposit_sell::*; diff --git a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs new file mode 100644 index 0000000..193883b --- /dev/null +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs @@ -0,0 +1,105 @@ +use anchor_lang::prelude::*; +use mpl_core::instructions::TransferV1Builder; +use mpl_core::types::UpdateAuthority; +use solana_program::program::invoke; + +use crate::{ + constants::*, + errors::MMMErrorCode, + state::{Pool, SellState}, + util::{check_allowlists_for_mpl_core, log_pool}, + AssetInterface, DepositSellArgs, IndexableAsset, +}; + +#[derive(Accounts)] +#[instruction(args:DepositSellArgs)] +pub struct MplCoreDepositSell<'info> { + #[account(mut)] + pub owner: Signer<'info>, + pub cosigner: Signer<'info>, + #[account( + mut, + seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()], + has_one = owner @ MMMErrorCode::InvalidOwner, + has_one = cosigner @ MMMErrorCode::InvalidCosigner, + bump + )] + pub pool: Box>, + /// CHECK: check asset later + pub asset: Box>, + #[account( + init_if_needed, + payer = owner, + seeds = [ + SELL_STATE_PREFIX.as_bytes(), + pool.key().as_ref(), + asset.key().as_ref(), + ], + space = SellState::LEN, + bump + )] + pub sell_state: Account<'info, SellState>, + + pub system_program: Program<'info, System>, + pub asset_program: Interface<'info, AssetInterface>, + /// CHECK: check collection later + collection: UncheckedAccount<'info>, +} + +pub fn handler(ctx: Context, args: DepositSellArgs) -> Result<()> { + let owner = &ctx.accounts.owner; + let asset = &ctx.accounts.asset; + let pool = &mut ctx.accounts.pool; + let sell_state = &mut ctx.accounts.sell_state; + let collection = &ctx.accounts.collection; + + if pool.using_shared_escrow() { + return Err(MMMErrorCode::InvalidAccountState.into()); + } + + let _ = check_allowlists_for_mpl_core(&pool.allowlists, asset, args.allowlist_aux)?; + + let transfer_asset_builder = TransferV1Builder::new() + .asset(asset.key()) + .payer(owner.key()) + .collection( + if let UpdateAuthority::Collection(collection_address) = asset.update_authority { + Some(collection_address) + } else { + None + }, + ) + .new_owner(pool.key()) + .instruction(); + + let mut account_infos = vec![ + asset.to_account_info(), + owner.to_account_info(), + pool.to_account_info(), + ]; + if collection.key != &Pubkey::default() { + if UpdateAuthority::Collection(collection.key()) != asset.update_authority { + return Err(MMMErrorCode::InvalidAssetCollection.into()); + } + account_infos.push(collection.to_account_info()); + } + + invoke(&transfer_asset_builder, account_infos.as_slice())?; + + pool.sellside_asset_amount = pool + .sellside_asset_amount + .checked_add(args.asset_amount) + .ok_or(MMMErrorCode::NumericOverflow)?; + + sell_state.pool = pool.key(); + sell_state.pool_owner = owner.key(); + sell_state.asset_mint = asset.key(); + sell_state.cosigner_annotation = pool.cosigner_annotation; + sell_state.asset_amount = sell_state + .asset_amount + .checked_add(args.asset_amount) + .ok_or(MMMErrorCode::NumericOverflow)?; + log_pool("post_mpl_core_deposit_sell", pool)?; + + Ok(()) +} diff --git a/programs/mmm/src/mpl-core.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs similarity index 96% rename from programs/mmm/src/mpl-core.rs rename to programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs index 11a7998..158523e 100644 --- a/programs/mmm/src/mpl-core.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs @@ -2,7 +2,6 @@ use mpl_core::ID; use solana_program::pubkey::Pubkey; use std::ops::Deref; -// TODO: move to shared library #[derive(Clone)] pub struct AssetInterface; diff --git a/programs/mmm/src/lib.rs b/programs/mmm/src/lib.rs index ba7932f..d1c1adc 100644 --- a/programs/mmm/src/lib.rs +++ b/programs/mmm/src/lib.rs @@ -152,4 +152,11 @@ pub mod mmm { ) -> Result<()> { instructions::ext_withdraw_sell::handler(ctx, args) } + + pub fn mpl_core_deposit_sell( + ctx: Context, + args: DepositSellArgs, + ) -> Result<()> { + instructions::mpl_core_deposit_sell::handler(ctx, args) + } } diff --git a/programs/mmm/src/state.rs b/programs/mmm/src/state.rs index 043d2c1..b6384e7 100644 --- a/programs/mmm/src/state.rs +++ b/programs/mmm/src/state.rs @@ -11,6 +11,8 @@ pub const ALLOWLIST_KIND_MINT: u8 = 2; pub const ALLOWLIST_KIND_MCC: u8 = 3; pub const ALLOWLIST_KIND_METADATA: u8 = 4; pub const ALLOWLIST_KIND_GROUP: u8 = 5; +// this is should only be used by mpl core with update_authority as collection. +pub const ALLOWLIST_KIND_UPGRADE_AUTHORITY: u8 = 6; // ANY nft will pass the allowlist check, please make sure to use cosigner to check NFT validity pub const ALLOWLIST_KIND_ANY: u8 = u8::MAX; diff --git a/programs/mmm/src/util.rs b/programs/mmm/src/util.rs index bc72f36..9b775fa 100644 --- a/programs/mmm/src/util.rs +++ b/programs/mmm/src/util.rs @@ -6,12 +6,14 @@ use crate::{ }, errors::MMMErrorCode, state::*, + IndexableAsset, }; use anchor_lang::{prelude::*, solana_program::log::sol_log_data}; use anchor_spl::token_interface::Mint; use m2_interface::{ withdraw_by_mmm_ix_with_program_id, WithdrawByMMMArgs, WithdrawByMmmIxArgs, WithdrawByMmmKeys, }; +use mpl_core::types::UpdateAuthority; use mpl_token_metadata::{ accounts::{MasterEdition, Metadata}, types::TokenStandard, @@ -783,6 +785,62 @@ pub fn check_allowlists_for_mint_ext( Err(MMMErrorCode::InvalidAllowLists.into()) } +pub fn check_allowlists_for_mpl_core<'info>( + allowlists: &[Allowlist], + asset: &Box>, + allowlist_aux: Option, +) -> Result<()> { + if allowlists + .iter() + .any(|&val| val.kind == ALLOWLIST_KIND_METADATA) + { + // If allowlist_aux is not passed in, do not validate URI. + if let Some(ref aux_key) = allowlist_aux { + // Handle URI padding. + if !asset.uri.trim().starts_with(aux_key) { + msg!( + "Failed metadata validation. Expected URI: |{}| but got |{}|", + *aux_key, + asset.uri + ); + return Err(MMMErrorCode::UnexpectedMetadataUri.into()); + } + } + } + + for allowlist_val in allowlists.iter() { + match allowlist_val.kind { + ALLOWLIST_KIND_EMPTY => { + continue; + } + ALLOWLIST_KIND_ANY => { + // any is a special case, we don't need to check anything else + return Ok(()); + } + ALLOWLIST_KIND_UPGRADE_AUTHORITY => { + if let UpdateAuthority::Collection(collection_address) = asset.update_authority { + if collection_address != allowlist_val.value { + return Err(MMMErrorCode::InvalidAllowLists.into()); + } + } else { + return Err(MMMErrorCode::InvalidAllowLists.into()); + } + } + ALLOWLIST_KIND_MINT => { + if asset.key() != allowlist_val.value { + return Err(MMMErrorCode::InvalidAllowLists.into()); + } + } + _ => { + return Err(MMMErrorCode::InvalidAllowLists.into()); + } + } + } + + // at the end, we didn't find a match, thus return err + Err(MMMErrorCode::InvalidAllowLists.into()) +} + pub fn assert_and_get_valid_group(mint: &AccountInfo) -> Result> { let borrowed_data = mint.data.borrow(); let mint_deserialized = StateWithExtensions::::unpack(&borrowed_data)?; diff --git a/sdk/src/idl/mmm.ts b/sdk/src/idl/mmm.ts index e91e7c6..c1987fe 100644 --- a/sdk/src/idl/mmm.ts +++ b/sdk/src/idl/mmm.ts @@ -1908,6 +1908,59 @@ export type Mmm = { } } ] + }, + { + "name": "mplCoreDepositSell", + "accounts": [ + { + "name": "owner", + "isMut": true, + "isSigner": true + }, + { + "name": "cosigner", + "isMut": false, + "isSigner": true + }, + { + "name": "pool", + "isMut": true, + "isSigner": false + }, + { + "name": "asset", + "isMut": false, + "isSigner": false + }, + { + "name": "sellState", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "assetProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "collection", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": "DepositSellArgs" + } + } + ] } ], "accounts": [ @@ -2567,6 +2620,11 @@ export type Mmm = { "code": 6031, "name": "InvalidTokenMemberExtension", "msg": "Invalid token member extensions" + }, + { + "code": 6032, + "name": "InvalidAssetCollection", + "msg": "Invalid asset collection" } ] }; @@ -4481,6 +4539,59 @@ export const IDL: Mmm = { } } ] + }, + { + "name": "mplCoreDepositSell", + "accounts": [ + { + "name": "owner", + "isMut": true, + "isSigner": true + }, + { + "name": "cosigner", + "isMut": false, + "isSigner": true + }, + { + "name": "pool", + "isMut": true, + "isSigner": false + }, + { + "name": "asset", + "isMut": false, + "isSigner": false + }, + { + "name": "sellState", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "assetProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "collection", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": "DepositSellArgs" + } + } + ] } ], "accounts": [ @@ -5140,6 +5251,11 @@ export const IDL: Mmm = { "code": 6031, "name": "InvalidTokenMemberExtension", "msg": "Invalid token member extensions" + }, + { + "code": 6032, + "name": "InvalidAssetCollection", + "msg": "Invalid asset collection" } ] }; From 81006220cc8e22ba2473c5538466ddadd4bb03dd Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Tue, 23 Apr 2024 23:53:10 -0700 Subject: [PATCH 3/9] add sdk support --- package.json | 1 + pnpm-lock.yaml | 13 ++++ .../mpl_core_asset/mpl_core_deposit_sell.rs | 4 +- sdk/src/idl/mmm.ts | 12 ++-- sdk/src/mmmClient.ts | 62 +++++++++++++++++-- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 1a3dc79..a6d538e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dependencies": { "@magiceden-oss/open_creator_protocol": "^0.3.2", "@metaplex-foundation/js": "^0.19.4", + "@metaplex-foundation/mpl-core": "^0.4.5", "@metaplex-foundation/mpl-token-auth-rules": "^2.0.0", "@metaplex-foundation/mpl-token-metadata": "^3.1.2", "@metaplex-foundation/umi": "^0.8.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c8cff2..b7cfac3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@metaplex-foundation/js': specifier: ^0.19.4 version: 0.19.5(fastestsmallesttextencoderdecoder@1.0.22) + '@metaplex-foundation/mpl-core': + specifier: ^0.4.5 + version: 0.4.5(@metaplex-foundation/umi@0.8.10)(@noble/hashes@1.3.3) '@metaplex-foundation/mpl-token-auth-rules': specifier: ^2.0.0 version: 2.0.0(fastestsmallesttextencoderdecoder@1.0.22) @@ -817,6 +820,16 @@ packages: - supports-color - utf-8-validate + /@metaplex-foundation/mpl-core@0.4.5(@metaplex-foundation/umi@0.8.10)(@noble/hashes@1.3.3): + resolution: {integrity: sha512-/7aC9h9cMGduAqw8fntfdpR2dYzPad8q/6GkFULnQgr5lxw7clod/6+7jjuS3yqYNBf5RlAk1zat41B+mcGZ1g==} + peerDependencies: + '@metaplex-foundation/umi': '>=0.8.2 < 1' + '@noble/hashes': ^1.3.1 + dependencies: + '@metaplex-foundation/umi': 0.8.10 + '@noble/hashes': 1.3.3 + dev: false + /@metaplex-foundation/mpl-core@0.6.1: resolution: {integrity: sha512-6R4HkfAqU2EUakNbVLcCmka0YuQTLGTbHJ62ig765+NRWuB2HNGUQ1HfHcRsGnyxhlCvwKK79JE01XUjFE+dzw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. diff --git a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs index 193883b..ca96d5d 100644 --- a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs @@ -39,11 +39,11 @@ pub struct MplCoreDepositSell<'info> { bump )] pub sell_state: Account<'info, SellState>, + /// CHECK: check collection later + collection: UncheckedAccount<'info>, pub system_program: Program<'info, System>, pub asset_program: Interface<'info, AssetInterface>, - /// CHECK: check collection later - collection: UncheckedAccount<'info>, } pub fn handler(ctx: Context, args: DepositSellArgs) -> Result<()> { diff --git a/sdk/src/idl/mmm.ts b/sdk/src/idl/mmm.ts index c1987fe..264922e 100644 --- a/sdk/src/idl/mmm.ts +++ b/sdk/src/idl/mmm.ts @@ -1938,17 +1938,17 @@ export type Mmm = { "isSigner": false }, { - "name": "systemProgram", + "name": "collection", "isMut": false, "isSigner": false }, { - "name": "assetProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "collection", + "name": "assetProgram", "isMut": false, "isSigner": false } @@ -4569,17 +4569,17 @@ export const IDL: Mmm = { "isSigner": false }, { - "name": "systemProgram", + "name": "collection", "isMut": false, "isSigner": false }, { - "name": "assetProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "collection", + "name": "assetProgram", "isMut": false, "isSigner": false } diff --git a/sdk/src/mmmClient.ts b/sdk/src/mmmClient.ts index 828f519..05db20d 100644 --- a/sdk/src/mmmClient.ts +++ b/sdk/src/mmmClient.ts @@ -45,6 +45,12 @@ import { MintExtTransferHookProvider, TransferHookProvider, } from './transferHookProvider'; +import { + collectionAddress, + deserializeAssetV1, + MPL_CORE_PROGRAM_ID, +} from '@metaplex-foundation/mpl-core'; +import { lamports, publicKey, RpcAccount } from '@metaplex-foundation/umi'; export const getEmptyAllowLists = (num: number) => { const emptyAllowList = { @@ -58,6 +64,26 @@ export const MMMProgramID = new PublicKey( 'mmm3XBJg5gk8XJxEKBvdgptZz6SgK4tXvn36sodowMc', ); +export function isMplCoreAsset( + account: anchor.web3.AccountInfo, +): boolean { + return account?.owner.equals(new PublicKey(MPL_CORE_PROGRAM_ID)) ?? false; +} + +export function convertAccountInfoToRpcAccount( + assetAddress: PublicKey, + accountInfo: anchor.web3.AccountInfo, +): RpcAccount { + return { + executable: accountInfo.executable, + owner: MPL_CORE_PROGRAM_ID, + lamports: lamports(accountInfo.lamports), + rentEpoch: accountInfo.rentEpoch, + publicKey: publicKey(assetAddress), + data: accountInfo.data, + }; +} + const dummyKeypair = new anchor.Wallet(new anchor.web3.Keypair()); type MmmMethodsNamespace = anchor.MethodsNamespace; @@ -663,6 +689,37 @@ export class MMMClient { transferHookProvider?: TransferHookProvider, ): Promise { if (!this.poolData) throw MMMClient.ErrPoolDataEmpty; + + let builder: + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + + const mintOrCoreAsset = await this.conn.getAccountInfo(assetMint); + if (!!mintOrCoreAsset && isMplCoreAsset(mintOrCoreAsset)) { + const asset = deserializeAssetV1( + convertAccountInfoToRpcAccount(assetMint, mintOrCoreAsset), + ); + builder = this.program.methods.mplCoreDepositSell(args).accountsStrict({ + owner: this.poolData.owner, + cosigner: this.poolData.cosigner, + pool: this.poolData.pool, + asset: assetMint, + sellState: getMMMSellStatePDA( + MMMProgramID, + this.poolData.pool, + assetMint, + ).key, + collection: collectionAddress(asset) || PublicKey.default, + systemProgram: SystemProgram.programId, + assetProgram: MPL_CORE_PROGRAM_ID, + }); + + return await builder.instruction(); + } + const assetMetadata = this.mpl.nfts().pdas().metadata({ mint: assetMint }); const mintContext = metadataProvider ?? @@ -693,11 +750,6 @@ export class MMMClient { ); const ocpMintState = mintContext.mintState; - let builder: - | ReturnType - | ReturnType - | ReturnType - | ReturnType; if (doesTokenExtensionExist(mintContext)) { builder = this.program.methods.extDepositSell(args).accountsStrict({ From 56fed32fe739f88d36de660a364095598c0ae3db Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Wed, 24 Apr 2024 10:36:57 -0700 Subject: [PATCH 4/9] format --- programs/mmm/src/instructions/mod.rs | 4 ++-- programs/mmm/src/instructions/mpl_core_asset/mod.rs | 4 ++-- programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/programs/mmm/src/instructions/mod.rs b/programs/mmm/src/instructions/mod.rs index 8201825..a12cc04 100644 --- a/programs/mmm/src/instructions/mod.rs +++ b/programs/mmm/src/instructions/mod.rs @@ -3,16 +3,16 @@ pub mod admin; pub mod ext_vanilla; pub mod mip1; +pub mod mpl_core_asset; pub mod ocp; pub mod vanilla; -pub mod mpl_core_asset; pub use admin::*; pub use ext_vanilla::*; pub use mip1::*; +pub use mpl_core_asset::*; pub use ocp::*; pub use vanilla::*; -pub use mpl_core_asset::*; use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; diff --git a/programs/mmm/src/instructions/mpl_core_asset/mod.rs b/programs/mmm/src/instructions/mpl_core_asset/mod.rs index f6d59c8..5b379d4 100644 --- a/programs/mmm/src/instructions/mpl_core_asset/mod.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mod.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] -pub mod mpl_core_wrap; pub mod mpl_core_deposit_sell; +pub mod mpl_core_wrap; -pub use mpl_core_wrap::*; pub use mpl_core_deposit_sell::*; +pub use mpl_core_wrap::*; diff --git a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs index 158523e..f725341 100644 --- a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs @@ -36,4 +36,4 @@ impl Deref for IndexableAsset { fn deref(&self) -> &Self::Target { &self.0 } -} \ No newline at end of file +} From 80d8baf9d79c310241829e9bb60698841d84b937 Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Wed, 24 Apr 2024 16:23:12 -0700 Subject: [PATCH 5/9] add tests --- .../mpl_core_asset/mpl_core_deposit_sell.rs | 5 +- programs/mmm/src/state.rs | 7 +- programs/mmm/src/util.rs | 3 +- sdk/src/constants.ts | 1 + sdk/src/idl/mmm.ts | 4 +- tests/mmm-mpl-core.spec.ts | 227 ++++++++++++++++++ tests/utils/index.ts | 1 + tests/utils/mpl_core.ts | 160 ++++++++++++ 8 files changed, 401 insertions(+), 7 deletions(-) create mode 100644 tests/mmm-mpl-core.spec.ts create mode 100644 tests/utils/mpl_core.ts diff --git a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs index ca96d5d..f41ae91 100644 --- a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs @@ -25,7 +25,10 @@ pub struct MplCoreDepositSell<'info> { bump )] pub pool: Box>, - /// CHECK: check asset later + #[account( + mut, + constraint = asset.to_account_info().owner == asset_program.key, + )] pub asset: Box>, #[account( init_if_needed, diff --git a/programs/mmm/src/state.rs b/programs/mmm/src/state.rs index b6384e7..353758f 100644 --- a/programs/mmm/src/state.rs +++ b/programs/mmm/src/state.rs @@ -12,7 +12,7 @@ pub const ALLOWLIST_KIND_MCC: u8 = 3; pub const ALLOWLIST_KIND_METADATA: u8 = 4; pub const ALLOWLIST_KIND_GROUP: u8 = 5; // this is should only be used by mpl core with update_authority as collection. -pub const ALLOWLIST_KIND_UPGRADE_AUTHORITY: u8 = 6; +pub const ALLOWLIST_KIND_UPDATE_AUTHORITY: u8 = 6; // ANY nft will pass the allowlist check, please make sure to use cosigner to check NFT validity pub const ALLOWLIST_KIND_ANY: u8 = u8::MAX; @@ -29,10 +29,11 @@ impl Allowlist { // kind == 3: verified MCC // kind == 4: metadata // kind == 5: group extension - // kind == 6,7,... will be supported in the future + // kind == 6: upgrade authority + // kind == 7,8,... will be supported in the future // kind == 255: any pub fn valid(&self) -> bool { - if self.kind > ALLOWLIST_KIND_GROUP && self.kind != ALLOWLIST_KIND_ANY { + if self.kind > ALLOWLIST_KIND_UPDATE_AUTHORITY && self.kind != ALLOWLIST_KIND_ANY { return false; } if self.kind != 0 && self.kind != ALLOWLIST_KIND_ANY { diff --git a/programs/mmm/src/util.rs b/programs/mmm/src/util.rs index 9b775fa..2e13277 100644 --- a/programs/mmm/src/util.rs +++ b/programs/mmm/src/util.rs @@ -817,11 +817,12 @@ pub fn check_allowlists_for_mpl_core<'info>( // any is a special case, we don't need to check anything else return Ok(()); } - ALLOWLIST_KIND_UPGRADE_AUTHORITY => { + ALLOWLIST_KIND_UPDATE_AUTHORITY => { if let UpdateAuthority::Collection(collection_address) = asset.update_authority { if collection_address != allowlist_val.value { return Err(MMMErrorCode::InvalidAllowLists.into()); } + return Ok(()); } else { return Err(MMMErrorCode::InvalidAllowLists.into()); } diff --git a/sdk/src/constants.ts b/sdk/src/constants.ts index 8ab215e..b7f68f5 100644 --- a/sdk/src/constants.ts +++ b/sdk/src/constants.ts @@ -18,6 +18,7 @@ export enum AllowlistKind { mcc = 3, metadata = 4, group = 5, + update_authority = 6, any = 255, } diff --git a/sdk/src/idl/mmm.ts b/sdk/src/idl/mmm.ts index 264922e..c9814c8 100644 --- a/sdk/src/idl/mmm.ts +++ b/sdk/src/idl/mmm.ts @@ -1929,7 +1929,7 @@ export type Mmm = { }, { "name": "asset", - "isMut": false, + "isMut": true, "isSigner": false }, { @@ -4560,7 +4560,7 @@ export const IDL: Mmm = { }, { "name": "asset", - "isMut": false, + "isMut": true, "isSigner": false }, { diff --git a/tests/mmm-mpl-core.spec.ts b/tests/mmm-mpl-core.spec.ts new file mode 100644 index 0000000..e392fe1 --- /dev/null +++ b/tests/mmm-mpl-core.spec.ts @@ -0,0 +1,227 @@ +import * as anchor from '@project-serum/anchor'; +import { ComputeBudgetProgram, Keypair, SystemProgram } from '@solana/web3.js'; +import { + AllowlistKind, + IDL, + MMMProgramID, + Mmm, + getMMMSellStatePDA, +} from '../sdk/src'; +import { + airdrop, + createPool, + createTestMplCoreAsset, + getEmptyAllowLists, + getTestMplCoreAsset, +} from './utils'; +import { publicKey } from '@metaplex-foundation/umi'; +import { toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters'; +import { MPL_CORE_PROGRAM_ID } from '@metaplex-foundation/mpl-core'; +import { assert, expect } from 'chai'; +import { ProgramError } from '@project-serum/anchor'; + +describe('mmm-mpl-core', () => { + const { connection } = anchor.AnchorProvider.env(); + const wallet = new anchor.Wallet(Keypair.generate()); + const creator = Keypair.generate(); + const provider = new anchor.AnchorProvider(connection, wallet, { + commitment: 'confirmed', + }); + const program = new anchor.Program( + IDL, + MMMProgramID, + provider, + ) as anchor.Program; + const cosigner = Keypair.generate(); + + beforeEach(async () => { + await airdrop(connection, wallet.publicKey, 10); + await airdrop(connection, creator.publicKey, 10); + }); + + it('can deposit sell - asset with collection', async () => { + const { asset, collection } = await createTestMplCoreAsset( + publicKey(wallet.publicKey), + { + collectionConfig: {}, // use default collection config + }, + ); + + const allowlists = [ + { + value: toWeb3JsPublicKey(collection!.publicKey), + kind: AllowlistKind.update_authority, + }, + ...getEmptyAllowLists(5), + ]; + const poolData = await createPool(program, { + owner: wallet.publicKey, + cosigner, + allowlists, + }); + + const { key: sellState } = getMMMSellStatePDA( + program.programId, + poolData.poolKey, + toWeb3JsPublicKey(asset.publicKey), + ); + + await program.methods + .mplCoreDepositSell({ + assetAmount: new anchor.BN(1), + allowlistAux: null, + }) + .accountsStrict({ + owner: wallet.publicKey, + cosigner: cosigner.publicKey, + pool: poolData.poolKey, + asset: asset.publicKey, + sellState, + collection: collection!.publicKey, + systemProgram: SystemProgram.programId, + assetProgram: MPL_CORE_PROGRAM_ID, + }) + .preInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ + units: 1_000_000, + }), + ]) + .signers([cosigner]) + .rpc({ skipPreflight: true }); + + const [refreshedAsset, sellStateAccount] = await Promise.all([ + getTestMplCoreAsset(asset.publicKey), + program.account.sellState.fetch(sellState), + ]); + + // Verify sell state account + assert.equal(sellStateAccount.assetAmount.toNumber(), 1); + assert.equal( + sellStateAccount.assetMint.toString(), + asset.publicKey.toString(), + ); + + // Verify asset account. + assert.equal(refreshedAsset.owner.toString(), poolData.poolKey.toString()); + }); + + it.only("can't deposit sell - asset from other collection", async () => { + const { asset, collection } = await createTestMplCoreAsset( + publicKey(wallet.publicKey), + { + collectionConfig: {}, // use default collection config + }, + ); + + const poolData = await createPool(program, { + owner: wallet.publicKey, + cosigner, + allowlists: [ + { + value: Keypair.generate().publicKey, // different collection + kind: AllowlistKind.update_authority, + }, + ...getEmptyAllowLists(5), + ], + }); + + const { key: sellState } = getMMMSellStatePDA( + program.programId, + poolData.poolKey, + toWeb3JsPublicKey(asset.publicKey), + ); + + try { + await program.methods + .mplCoreDepositSell({ + assetAmount: new anchor.BN(1), + allowlistAux: null, + }) + .accountsStrict({ + owner: wallet.publicKey, + cosigner: cosigner.publicKey, + pool: poolData.poolKey, + asset: asset.publicKey, + sellState, + collection: collection!.publicKey, + systemProgram: SystemProgram.programId, + assetProgram: MPL_CORE_PROGRAM_ID, + }) + .preInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ + units: 1_000_000, + }), + ]) + .signers([cosigner]) + .rpc({ skipPreflight: true }); + } catch (e) { + expect(e).to.be.instanceOf(ProgramError); + const err = e as ProgramError; + + assert.strictEqual(err.msg, 'invalid allowlists'); + assert.strictEqual(err.code, 6001); + } + + const refreshedAsset = await getTestMplCoreAsset(asset.publicKey); + + // Verify asset account. + assert.equal(refreshedAsset.owner.toString(), wallet.publicKey.toString()); + }); + + it("can't deposit sell - no collection", async () => { + const { asset } = await createTestMplCoreAsset( + publicKey(wallet.publicKey), + { + collectionConfig: undefined, // no collection attached + }, + ); + + const poolData = await createPool(program, { + owner: wallet.publicKey, + cosigner, + ...getEmptyAllowLists(6), + }); + + const { key: sellState } = getMMMSellStatePDA( + program.programId, + poolData.poolKey, + toWeb3JsPublicKey(asset.publicKey), + ); + + try { + await program.methods + .mplCoreDepositSell({ + assetAmount: new anchor.BN(1), + allowlistAux: null, + }) + .accountsStrict({ + owner: wallet.publicKey, + cosigner: cosigner.publicKey, + pool: poolData.poolKey, + asset: asset.publicKey, + sellState, + collection: SystemProgram.programId, // no collection + systemProgram: SystemProgram.programId, + assetProgram: MPL_CORE_PROGRAM_ID, + }) + .preInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ + units: 1_000_000, + }), + ]) + .signers([cosigner]) + .rpc({ skipPreflight: true }); + } catch (e) { + expect(e).to.be.instanceOf(ProgramError); + const err = e as ProgramError; + + assert.strictEqual(err.msg, 'invalid allowlists'); + assert.strictEqual(err.code, 6001); + } + + const refreshedAsset = await getTestMplCoreAsset(asset.publicKey); + + // Verify asset account. + assert.equal(refreshedAsset.owner.toString(), wallet.publicKey.toString()); + }); +}); diff --git a/tests/utils/index.ts b/tests/utils/index.ts index 85fd736..2db438e 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -4,3 +4,4 @@ export * from './mip1'; export * from './mmm'; export * from './nfts'; export * from './ocp'; +export * from './mpl_core'; diff --git a/tests/utils/mpl_core.ts b/tests/utils/mpl_core.ts new file mode 100644 index 0000000..afe9eba --- /dev/null +++ b/tests/utils/mpl_core.ts @@ -0,0 +1,160 @@ +import { + AssetV1, + CollectionV1, + createCollectionV1, + createV1, + fetchAssetV1, + fetchCollectionV1, + MPL_CORE_PROGRAM_ID, + mplCore, + PluginAuthorityPair, + pluginAuthorityPair, + ruleSet, +} from '@metaplex-foundation/mpl-core'; +import { + generateSigner, + sol, + publicKey, + PublicKey, + KeypairSigner, + Umi, +} from '@metaplex-foundation/umi'; +import assert from 'assert'; +import { createUmi } from '@metaplex-foundation/umi-bundle-tests'; + +// TODO: move to shared oss library +export interface AssetV1Result { + asset: AssetV1; + collection?: CollectionV1; +} + +export interface CollectionConfig { + collection?: KeypairSigner; + name?: string; + uri?: string; + plugins?: PluginAuthorityPair[]; +} + +export interface AssetConfig { + asset?: KeypairSigner; + name?: string; + uri?: string; + owner?: PublicKey; + collection?: PublicKey; + plugins?: PluginAuthorityPair[]; +} + +export interface CreateCoreAssetArgs { + collectionConfig?: CollectionConfig; + assetConfig?: AssetConfig; +} + +export async function createTestMplCoreAsset( + ownerAddress: PublicKey, + args: CreateCoreAssetArgs = {}, // default no collection attached +): Promise { + const umi = (await createUmi('http://localhost:8899', undefined, sol(1))).use( + mplCore(), + ); + + let collectionPublicKey: PublicKey | undefined = undefined; + if (args.collectionConfig) { + collectionPublicKey = await createTestMplCoreCollection( + umi, + args.collectionConfig, + ); + } + + const assetConfig = args.assetConfig; + const assetSigner = generateSigner(umi); + collectionPublicKey = assetConfig?.collection ?? collectionPublicKey; + + await createV1(umi, { + asset: assetConfig?.asset ?? assetSigner, + name: assetConfig?.name ?? 'Test asset', + uri: assetConfig?.uri ?? 'https://example.com/my-nft.json', + owner: publicKey(ownerAddress), + collection: assetConfig?.collection ?? collectionPublicKey, + plugins: assetConfig?.plugins ?? [getDefaultAssetRoyaltyPlugin()], + }).sendAndConfirm(umi); + + const asset = await fetchAssetV1(umi, assetSigner.publicKey); + assert.equal(asset.owner, ownerAddress); + assert.equal(asset.header.owner, MPL_CORE_PROGRAM_ID); + + const collection = collectionPublicKey + ? await fetchCollectionV1(umi, collectionPublicKey) + : undefined; + + if (collection) { + assert.equal(asset.updateAuthority.type, 'Collection'); + assert.equal(asset.updateAuthority.address, collection!.publicKey); + } + + return { + asset: asset, + ...(collection ? { collection } : {}), + }; +} + +export async function createTestMplCoreCollection( + umi: Umi, + config: CollectionConfig, +): Promise { + const collectionSigner = generateSigner(umi); + + await createCollectionV1(umi, { + collection: config.collection ?? collectionSigner, + name: config.name ?? 'My NFT', + uri: config.uri ?? 'https://example.com/my-nft.json', + plugins: config.plugins ?? [getDefaultCollectionRoyaltyPlugin()], + }).sendAndConfirm(umi); + return collectionSigner.publicKey; +} + +export async function getTestMplCoreAsset(assetAddress: PublicKey) { + const umi = await createUmi('http://localhost:8899'); + umi.use(mplCore()); + const assetV1 = await fetchAssetV1(umi, assetAddress); + return assetV1; +} + +export function getDefaultAssetRoyaltyPlugin(): PluginAuthorityPair { + return pluginAuthorityPair({ + type: 'Royalties', + data: { + basisPoints: 200, + creators: [ + { + address: publicKey('11111111111111111111111111111111'), + percentage: 30, + }, + { + address: publicKey('11111111111111111111111111111112'), + percentage: 70, + }, + ], + ruleSet: ruleSet('None'), + }, + }); +} + +export function getDefaultCollectionRoyaltyPlugin(): PluginAuthorityPair { + return pluginAuthorityPair({ + type: 'Royalties', + data: { + basisPoints: 500, + creators: [ + { + address: publicKey('11111111111111111111111111111111'), + percentage: 20, + }, + { + address: publicKey('11111111111111111111111111111112'), + percentage: 80, + }, + ], + ruleSet: ruleSet('None'), // Compatibility rule set + }, + }); +} From 771ee631633112d76b9bbd22445374cccd028151 Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Wed, 24 Apr 2024 16:25:58 -0700 Subject: [PATCH 6/9] remove only --- tests/mmm-mpl-core.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mmm-mpl-core.spec.ts b/tests/mmm-mpl-core.spec.ts index e392fe1..6d9c191 100644 --- a/tests/mmm-mpl-core.spec.ts +++ b/tests/mmm-mpl-core.spec.ts @@ -105,7 +105,7 @@ describe('mmm-mpl-core', () => { assert.equal(refreshedAsset.owner.toString(), poolData.poolKey.toString()); }); - it.only("can't deposit sell - asset from other collection", async () => { + it("can't deposit sell - asset from other collection", async () => { const { asset, collection } = await createTestMplCoreAsset( publicKey(wallet.publicKey), { From 6906bd77ef84b909454a8df4af38a90bc386dcd8 Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Wed, 24 Apr 2024 16:36:26 -0700 Subject: [PATCH 7/9] add missing metadata allowlist --- programs/mmm/src/util.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/programs/mmm/src/util.rs b/programs/mmm/src/util.rs index 2e13277..fd89826 100644 --- a/programs/mmm/src/util.rs +++ b/programs/mmm/src/util.rs @@ -832,6 +832,11 @@ pub fn check_allowlists_for_mpl_core<'info>( return Err(MMMErrorCode::InvalidAllowLists.into()); } } + ALLOWLIST_KIND_METADATA => { + // Do not validate URI here, as we already did it above. + // These checks are separate since allowlist values are unioned together. + continue; + } _ => { return Err(MMMErrorCode::InvalidAllowLists.into()); } From 755fc22d84de8987cd924b083aad9e0a7acb1a74 Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Wed, 24 Apr 2024 17:05:25 -0700 Subject: [PATCH 8/9] format --- .../src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs index f41ae91..644135f 100644 --- a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs @@ -28,7 +28,7 @@ pub struct MplCoreDepositSell<'info> { #[account( mut, constraint = asset.to_account_info().owner == asset_program.key, - )] + )] pub asset: Box>, #[account( init_if_needed, From c53c0a967dc57d15a2cc9c431d682b065725e61a Mon Sep 17 00:00:00 2001 From: JeremyLi28 Date: Thu, 25 Apr 2024 16:11:49 -0700 Subject: [PATCH 9/9] address comments --- .../mpl_core_asset/mpl_core_deposit_sell.rs | 15 ++++++--- programs/mmm/src/lib.rs | 2 +- programs/mmm/src/state.rs | 5 ++- programs/mmm/src/util.rs | 11 ++----- sdk/src/constants.ts | 2 +- sdk/src/idl/mmm.ts | 32 ++++++++++++++++-- sdk/src/mmmClient.ts | 33 +++++++++++-------- tests/mmm-mpl-core.spec.ts | 7 ++-- 8 files changed, 68 insertions(+), 39 deletions(-) diff --git a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs index 644135f..349393d 100644 --- a/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs +++ b/programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs @@ -8,11 +8,16 @@ use crate::{ errors::MMMErrorCode, state::{Pool, SellState}, util::{check_allowlists_for_mpl_core, log_pool}, - AssetInterface, DepositSellArgs, IndexableAsset, + AssetInterface, IndexableAsset, }; +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct MplCoreDepositSellArgs { + pub allowlist_aux: Option, +} + #[derive(Accounts)] -#[instruction(args:DepositSellArgs)] +#[instruction(args:MplCoreDepositSellArgs)] pub struct MplCoreDepositSell<'info> { #[account(mut)] pub owner: Signer<'info>, @@ -49,7 +54,7 @@ pub struct MplCoreDepositSell<'info> { pub asset_program: Interface<'info, AssetInterface>, } -pub fn handler(ctx: Context, args: DepositSellArgs) -> Result<()> { +pub fn handler(ctx: Context, args: MplCoreDepositSellArgs) -> Result<()> { let owner = &ctx.accounts.owner; let asset = &ctx.accounts.asset; let pool = &mut ctx.accounts.pool; @@ -91,7 +96,7 @@ pub fn handler(ctx: Context, args: DepositSellArgs) -> Resul pool.sellside_asset_amount = pool .sellside_asset_amount - .checked_add(args.asset_amount) + .checked_add(1) .ok_or(MMMErrorCode::NumericOverflow)?; sell_state.pool = pool.key(); @@ -100,7 +105,7 @@ pub fn handler(ctx: Context, args: DepositSellArgs) -> Resul sell_state.cosigner_annotation = pool.cosigner_annotation; sell_state.asset_amount = sell_state .asset_amount - .checked_add(args.asset_amount) + .checked_add(1) .ok_or(MMMErrorCode::NumericOverflow)?; log_pool("post_mpl_core_deposit_sell", pool)?; diff --git a/programs/mmm/src/lib.rs b/programs/mmm/src/lib.rs index d1c1adc..d6eb790 100644 --- a/programs/mmm/src/lib.rs +++ b/programs/mmm/src/lib.rs @@ -155,7 +155,7 @@ pub mod mmm { pub fn mpl_core_deposit_sell( ctx: Context, - args: DepositSellArgs, + args: MplCoreDepositSellArgs, ) -> Result<()> { instructions::mpl_core_deposit_sell::handler(ctx, args) } diff --git a/programs/mmm/src/state.rs b/programs/mmm/src/state.rs index 353758f..18e901a 100644 --- a/programs/mmm/src/state.rs +++ b/programs/mmm/src/state.rs @@ -11,8 +11,7 @@ pub const ALLOWLIST_KIND_MINT: u8 = 2; pub const ALLOWLIST_KIND_MCC: u8 = 3; pub const ALLOWLIST_KIND_METADATA: u8 = 4; pub const ALLOWLIST_KIND_GROUP: u8 = 5; -// this is should only be used by mpl core with update_authority as collection. -pub const ALLOWLIST_KIND_UPDATE_AUTHORITY: u8 = 6; +pub const ALLOWLIST_KIND_MPL_CORE_COLLECTION: u8 = 6; // ANY nft will pass the allowlist check, please make sure to use cosigner to check NFT validity pub const ALLOWLIST_KIND_ANY: u8 = u8::MAX; @@ -33,7 +32,7 @@ impl Allowlist { // kind == 7,8,... will be supported in the future // kind == 255: any pub fn valid(&self) -> bool { - if self.kind > ALLOWLIST_KIND_UPDATE_AUTHORITY && self.kind != ALLOWLIST_KIND_ANY { + if self.kind > ALLOWLIST_KIND_MPL_CORE_COLLECTION && self.kind != ALLOWLIST_KIND_ANY { return false; } if self.kind != 0 && self.kind != ALLOWLIST_KIND_ANY { diff --git a/programs/mmm/src/util.rs b/programs/mmm/src/util.rs index fd89826..39ac7b3 100644 --- a/programs/mmm/src/util.rs +++ b/programs/mmm/src/util.rs @@ -785,9 +785,9 @@ pub fn check_allowlists_for_mint_ext( Err(MMMErrorCode::InvalidAllowLists.into()) } -pub fn check_allowlists_for_mpl_core<'info>( +pub fn check_allowlists_for_mpl_core( allowlists: &[Allowlist], - asset: &Box>, + asset: &IndexableAsset, allowlist_aux: Option, ) -> Result<()> { if allowlists @@ -817,7 +817,7 @@ pub fn check_allowlists_for_mpl_core<'info>( // any is a special case, we don't need to check anything else return Ok(()); } - ALLOWLIST_KIND_UPDATE_AUTHORITY => { + ALLOWLIST_KIND_MPL_CORE_COLLECTION => { if let UpdateAuthority::Collection(collection_address) = asset.update_authority { if collection_address != allowlist_val.value { return Err(MMMErrorCode::InvalidAllowLists.into()); @@ -827,11 +827,6 @@ pub fn check_allowlists_for_mpl_core<'info>( return Err(MMMErrorCode::InvalidAllowLists.into()); } } - ALLOWLIST_KIND_MINT => { - if asset.key() != allowlist_val.value { - return Err(MMMErrorCode::InvalidAllowLists.into()); - } - } ALLOWLIST_KIND_METADATA => { // Do not validate URI here, as we already did it above. // These checks are separate since allowlist values are unioned together. diff --git a/sdk/src/constants.ts b/sdk/src/constants.ts index b7f68f5..9cc4273 100644 --- a/sdk/src/constants.ts +++ b/sdk/src/constants.ts @@ -18,7 +18,7 @@ export enum AllowlistKind { mcc = 3, metadata = 4, group = 5, - update_authority = 6, + mpl_core_collection = 6, any = 255, } diff --git a/sdk/src/idl/mmm.ts b/sdk/src/idl/mmm.ts index c9814c8..a454c2e 100644 --- a/sdk/src/idl/mmm.ts +++ b/sdk/src/idl/mmm.ts @@ -1957,7 +1957,7 @@ export type Mmm = { { "name": "args", "type": { - "defined": "DepositSellArgs" + "defined": "MplCoreDepositSellArgs" } } ] @@ -2289,6 +2289,20 @@ export type Mmm = { ] } }, + { + "name": "MplCoreDepositSellArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "allowlistAux", + "type": { + "option": "string" + } + } + ] + } + }, { "name": "SolOcpFulfillSellArgs", "type": { @@ -4588,7 +4602,7 @@ export const IDL: Mmm = { { "name": "args", "type": { - "defined": "DepositSellArgs" + "defined": "MplCoreDepositSellArgs" } } ] @@ -4920,6 +4934,20 @@ export const IDL: Mmm = { ] } }, + { + "name": "MplCoreDepositSellArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "allowlistAux", + "type": { + "option": "string" + } + } + ] + } + }, { "name": "SolOcpFulfillSellArgs", "type": { diff --git a/sdk/src/mmmClient.ts b/sdk/src/mmmClient.ts index 05db20d..2ef29ca 100644 --- a/sdk/src/mmmClient.ts +++ b/sdk/src/mmmClient.ts @@ -702,20 +702,25 @@ export class MMMClient { const asset = deserializeAssetV1( convertAccountInfoToRpcAccount(assetMint, mintOrCoreAsset), ); - builder = this.program.methods.mplCoreDepositSell(args).accountsStrict({ - owner: this.poolData.owner, - cosigner: this.poolData.cosigner, - pool: this.poolData.pool, - asset: assetMint, - sellState: getMMMSellStatePDA( - MMMProgramID, - this.poolData.pool, - assetMint, - ).key, - collection: collectionAddress(asset) || PublicKey.default, - systemProgram: SystemProgram.programId, - assetProgram: MPL_CORE_PROGRAM_ID, - }); + const mplCoreArgs = { + allowlistAux: args.allowlistAux, + } as anchor.IdlTypes['MplCoreDepositSellArgs']; + builder = this.program.methods + .mplCoreDepositSell(mplCoreArgs) + .accountsStrict({ + owner: this.poolData.owner, + cosigner: this.poolData.cosigner, + pool: this.poolData.pool, + asset: assetMint, + sellState: getMMMSellStatePDA( + MMMProgramID, + this.poolData.pool, + assetMint, + ).key, + collection: collectionAddress(asset) || PublicKey.default, + systemProgram: SystemProgram.programId, + assetProgram: MPL_CORE_PROGRAM_ID, + }); return await builder.instruction(); } diff --git a/tests/mmm-mpl-core.spec.ts b/tests/mmm-mpl-core.spec.ts index 6d9c191..5c2c0f9 100644 --- a/tests/mmm-mpl-core.spec.ts +++ b/tests/mmm-mpl-core.spec.ts @@ -50,7 +50,7 @@ describe('mmm-mpl-core', () => { const allowlists = [ { value: toWeb3JsPublicKey(collection!.publicKey), - kind: AllowlistKind.update_authority, + kind: AllowlistKind.mpl_core_collection, }, ...getEmptyAllowLists(5), ]; @@ -68,7 +68,6 @@ describe('mmm-mpl-core', () => { await program.methods .mplCoreDepositSell({ - assetAmount: new anchor.BN(1), allowlistAux: null, }) .accountsStrict({ @@ -119,7 +118,7 @@ describe('mmm-mpl-core', () => { allowlists: [ { value: Keypair.generate().publicKey, // different collection - kind: AllowlistKind.update_authority, + kind: AllowlistKind.mpl_core_collection, }, ...getEmptyAllowLists(5), ], @@ -134,7 +133,6 @@ describe('mmm-mpl-core', () => { try { await program.methods .mplCoreDepositSell({ - assetAmount: new anchor.BN(1), allowlistAux: null, }) .accountsStrict({ @@ -191,7 +189,6 @@ describe('mmm-mpl-core', () => { try { await program.methods .mplCoreDepositSell({ - assetAmount: new anchor.BN(1), allowlistAux: null, }) .accountsStrict({