Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MplCore] Deposit sell #93

Merged
merged 9 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions programs/mmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 2 additions & 0 deletions programs/mmm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,6 @@ pub enum MMMErrorCode {
InvalidTokenMetadataExtension, // 0x178e
#[msg("Invalid token member extensions")]
InvalidTokenMemberExtension, // 0x178f
#[msg("Invalid asset collection")]
InvalidAssetCollection,
}
2 changes: 2 additions & 0 deletions programs/mmm/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
pub mod admin;
pub mod ext_vanilla;
pub mod mip1;
pub mod mpl_core_asset;
pub mod ocp;
pub mod vanilla;

pub use admin::*;
pub use ext_vanilla::*;
pub use mip1::*;
pub use mpl_core_asset::*;
pub use ocp::*;
pub use vanilla::*;

Expand Down
7 changes: 7 additions & 0 deletions programs/mmm/src/instructions/mpl_core_asset/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![allow(missing_docs)]

pub mod mpl_core_deposit_sell;
pub mod mpl_core_wrap;

pub use mpl_core_deposit_sell::*;
pub use mpl_core_wrap::*;
113 changes: 113 additions & 0 deletions programs/mmm/src/instructions/mpl_core_asset/mpl_core_deposit_sell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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, IndexableAsset,
};

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct MplCoreDepositSellArgs {
pub allowlist_aux: Option<String>,
}

#[derive(Accounts)]
#[instruction(args:MplCoreDepositSellArgs)]
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<Account<'info, Pool>>,
#[account(
mut,
constraint = asset.to_account_info().owner == asset_program.key,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we check the ownership here? maybe cpi of transfer does this, good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will reply on cpi check for now

)]
pub asset: Box<Account<'info, IndexableAsset>>,
#[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>,
/// CHECK: check collection later
collection: UncheckedAccount<'info>,

pub system_program: Program<'info, System>,
pub asset_program: Interface<'info, AssetInterface>,
}

pub fn handler(ctx: Context<MplCoreDepositSell>, args: MplCoreDepositSellArgs) -> 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)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't need let _ =

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compiler seems will show some warning if I remove them


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(1)
.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(1)
.ok_or(MMMErrorCode::NumericOverflow)?;
log_pool("post_mpl_core_deposit_sell", pool)?;

Ok(())
}
39 changes: 39 additions & 0 deletions programs/mmm/src/instructions/mpl_core_asset/mpl_core_wrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use mpl_core::ID;
use solana_program::pubkey::Pubkey;
use std::ops::Deref;

#[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<Self> {
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
}
}
7 changes: 7 additions & 0 deletions programs/mmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,11 @@ pub mod mmm {
) -> Result<()> {
instructions::ext_withdraw_sell::handler(ctx, args)
}

pub fn mpl_core_deposit_sell(
ctx: Context<MplCoreDepositSell>,
args: MplCoreDepositSellArgs,
) -> Result<()> {
instructions::mpl_core_deposit_sell::handler(ctx, args)
}
}
6 changes: 4 additions & 2 deletions programs/mmm/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +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;
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;

Expand All @@ -27,10 +28,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_MPL_CORE_COLLECTION && self.kind != ALLOWLIST_KIND_ANY {
return false;
}
if self.kind != 0 && self.kind != ALLOWLIST_KIND_ANY {
Expand Down
59 changes: 59 additions & 0 deletions programs/mmm/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -783,6 +785,63 @@ pub fn check_allowlists_for_mint_ext(
Err(MMMErrorCode::InvalidAllowLists.into())
}

pub fn check_allowlists_for_mpl_core(
allowlists: &[Allowlist],
asset: &IndexableAsset,
allowlist_aux: Option<String>,
) -> Result<()> {
if allowlists
.iter()
.any(|&val| val.kind == ALLOWLIST_KIND_METADATA)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explore if we can make Core's onchain traits verifiable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be address in the future when we support onchain attribute offer for mpl core

{
// 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_MPL_CORE_COLLECTION => {
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());
}
}
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());
}
}
}

// 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<Option<Pubkey>> {
let borrowed_data = mint.data.borrow();
let mint_deserialized = StateWithExtensions::<Token22Mint>::unpack(&borrowed_data)?;
Expand Down
1 change: 1 addition & 0 deletions sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum AllowlistKind {
mcc = 3,
metadata = 4,
group = 5,
mpl_core_collection = 6,
any = 255,
}

Expand Down
Loading
Loading