Skip to content

Commit

Permalink
add support for payment proxies (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
madergaser authored Aug 6, 2024
1 parent 45939e1 commit 57a3050
Show file tree
Hide file tree
Showing 17 changed files with 321 additions and 34 deletions.
8 changes: 8 additions & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ address = "CZ1rQoAHSqWBoAEfqGsiLhgbM59dDrCWk3rnG5FXaoRV" # libreplex royalty enf
[[test.validator.clone]]
address = "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" # metaplex core program

[[test.validator.account]]
address = "9V5HWD1ap6mCDMhBoXU5SVcZZn9ihqJtoMQZsw5MTnoD" # example payment proxy
filename = './tests/deps/proxy.json'

[[test.validator.account]]
address = "AJtUEMcZv9DDG4EVd8ugG3duAnCmmmVa6xCEUV7FqFFd" # bad payment proxy, owned by invalid program
filename = "./tests/deps/invalid_proxy.json"

[[test.genesis]]
address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
program = "./tests/deps/spl_token_2022.so"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{
get_lp_fee_bp, get_sol_fee, get_sol_lp_fee, get_sol_total_price_and_next_price,
try_close_escrow, try_close_sell_state,
},
verify_referral::verify_referral,
SolFulfillBuyArgs,
};

Expand All @@ -41,14 +42,16 @@ pub struct ExtSolFulfillBuy<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
#[account(mut)]
/// CHECK: we will check that the referral matches the pool's referral
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
},
state::{Pool, SellState},
util::check_allowlists_for_mint_ext,
verify_referral::verify_referral,
SolFulfillSellArgs,
};

Expand All @@ -36,14 +37,16 @@ pub struct ExtSolFulfillSell<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
/// CHECK: we will check that the referral matches the pool's referral
#[account(mut)]
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
9 changes: 6 additions & 3 deletions programs/mmm/src/instructions/mip1/sol_mip1_fulfill_buy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
get_sol_lp_fee, get_sol_total_price_and_next_price, log_pool, pay_creator_fees_in_sol,
try_close_escrow, try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
};

// FulfillBuy means a seller wants to sell NFT/SFT into the pool
Expand All @@ -40,14 +41,16 @@ pub struct SolMip1FulfillBuy<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
#[account(mut)]
/// CHECK: we will check that the referral matches the pool's referral
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
9 changes: 6 additions & 3 deletions programs/mmm/src/instructions/mip1/sol_mip1_fulfill_sell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
get_metadata_royalty_bp, get_sol_fee, get_sol_lp_fee, get_sol_total_price_and_next_price,
log_pool, pay_creator_fees_in_sol, try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
};

#[derive(AnchorSerialize, AnchorDeserialize)]
Expand All @@ -43,14 +44,16 @@ pub struct SolMip1FulfillSell<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
/// CHECK: we will check that the referral matches the pool's referral
#[account(mut)]
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
get_sol_fee, get_sol_lp_fee, get_sol_total_price_and_next_price, log_pool,
pay_creator_fees_in_sol, try_close_escrow, try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
AssetInterface, IndexableAsset,
};

Expand All @@ -40,14 +41,16 @@ pub struct SolMplCoreFulfillBuy<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
#[account(mut)]
/// CHECK: we will check that the referral matches the pool's referral
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
get_metadata_royalty_bp, log_pool, pay_creator_fees_in_sol, try_close_pool,
try_close_sell_state,
},
verify_referral::verify_referral,
AssetInterface, IndexableAsset,
};

Expand All @@ -40,14 +41,16 @@ pub struct SolMplCoreFulfillSell<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
/// CHECK: we will check that the referral matches the pool's referral
#[account(mut)]
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
9 changes: 6 additions & 3 deletions programs/mmm/src/instructions/ocp/sol_ocp_fulfill_buy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
get_sol_total_price_and_next_price, log_pool, pay_creator_fees_in_sol, try_close_escrow,
try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
};

// FulfillBuy means a seller wants to sell NFT/SFT into the pool
Expand All @@ -38,14 +39,16 @@ pub struct SolOcpFulfillBuy<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
#[account(mut)]
/// CHECK: we will check that the referral matches the pool's referral
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
9 changes: 6 additions & 3 deletions programs/mmm/src/instructions/ocp/sol_ocp_fulfill_sell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
get_sol_lp_fee, get_sol_total_price_and_next_price, log_pool, pay_creator_fees_in_sol,
try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
};

#[derive(AnchorSerialize, AnchorDeserialize)]
Expand All @@ -42,14 +43,16 @@ pub struct SolOcpFulfillSell<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
/// CHECK: we will check that the referral matches the pool's referral
#[account(mut)]
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
9 changes: 6 additions & 3 deletions programs/mmm/src/instructions/vanilla/sol_fulfill_buy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
get_sol_total_price_and_next_price, log_pool, pay_creator_fees_in_sol, try_close_escrow,
try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
};

#[derive(AnchorSerialize, AnchorDeserialize)]
Expand All @@ -43,14 +44,16 @@ pub struct SolFulfillBuy<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
#[account(mut)]
/// CHECK: we will check that the referral matches the pool's referral
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
9 changes: 6 additions & 3 deletions programs/mmm/src/instructions/vanilla/sol_fulfill_sell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
check_allowlists_for_mint, get_metadata_royalty_bp, log_pool, pay_creator_fees_in_sol,
try_close_pool, try_close_sell_state,
},
verify_referral::verify_referral,
};

#[derive(AnchorSerialize, AnchorDeserialize)]
Expand All @@ -40,14 +41,16 @@ pub struct SolFulfillSell<'info> {
pub owner: UncheckedAccount<'info>,
#[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)]
pub cosigner: Signer<'info>,
/// CHECK: we will check that the referral matches the pool's referral
#[account(mut)]
#[account(
mut,
constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral,
)]
/// CHECK: use verify_referral to check the referral account
pub referral: UncheckedAccount<'info>,
#[account(
mut,
seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()],
has_one = owner @ MMMErrorCode::InvalidOwner,
has_one = referral @ MMMErrorCode::InvalidReferral,
has_one = cosigner @ MMMErrorCode::InvalidCosigner,
constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint,
constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired,
Expand Down
1 change: 1 addition & 0 deletions programs/mmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod errors;
pub mod instructions;
pub mod state;
pub mod util;
pub mod verify_referral;

use instructions::*;

Expand Down
35 changes: 35 additions & 0 deletions programs/mmm/src/verify_referral.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use anchor_lang::prelude::{AccountInfo, Pubkey};
use solana_program::pubkey;

use crate::state::Pool;

const PAYMENT_PROXY_PROGRAM_ID: Pubkey = pubkey!("mpxdRTRiAzvxz8dgW6LQYzDATtKQBx2f1VJ6qsU28hn");
const PAYMENT_PROXY_DISCRIMINATOR: [u8; 8] = [0xee, 0x4a, 0x13, 0x79, 0x5e, 0x99, 0xac, 0x48];
const PAYMENT_PROXY_MIN_LEN: usize = 512;

pub fn verify_referral(pool: &Pool, referral: &AccountInfo<'_>) -> bool {
// Check if the referral account is the one defined in the pool
if referral.key == &pool.referral {
// early return true since the referral is the one expected
return true;
}

// From now on we assume that the referral account is a payment proxy account with the referral
// as the authority.

// Check if the account is owned by expected program and that it has expected data length
if referral.owner != &PAYMENT_PROXY_PROGRAM_ID || referral.data_len() < PAYMENT_PROXY_MIN_LEN {
return false;
}

let data = referral.try_borrow_data().unwrap();
// Check if proxy account has correct discriminator
if data[0..8] != PAYMENT_PROXY_DISCRIMINATOR {
return false;
}
// Check if proxy account has correct authority
if &data[8..40] != pool.referral.as_ref() {
return false;
}
true
}
14 changes: 14 additions & 0 deletions tests/deps/invalid_proxy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"pubkey": "AJtUEMcZv9DDG4EVd8ugG3duAnCmmmVa6xCEUV7FqFFd",
"account": {
"lamports": 4454400,
"data": [
"7koTeV6ZrEiQg0IPEp/Uuttukll5Ybq4Tq0Fyv/p3NtUND3zmVFT1QIAAABpbwAhsbfNghq8fn9TdloaMFqlw+SMn5DN9AMUTEH879PtpgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"base64"
],
"owner": "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d",
"executable": false,
"rentEpoch": 18446744073709551615,
"space": 512
}
}
14 changes: 14 additions & 0 deletions tests/deps/proxy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"pubkey": "9V5HWD1ap6mCDMhBoXU5SVcZZn9ihqJtoMQZsw5MTnoD",
"account": {
"lamports": 4454400,
"data": [
"7koTeV6ZrEiQg0IPEp/Uuttukll5Ybq4Tq0Fyv/p3NtUND3zmVFT1QIAAABpbwAhsbfNghq8fn9TdloaMFqlw+SMn5DN9AMUTEH879PtpgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"base64"
],
"owner": "mpxdRTRiAzvxz8dgW6LQYzDATtKQBx2f1VJ6qsU28hn",
"executable": false,
"rentEpoch": 18446744073709551615,
"space": 512
}
}
Loading

0 comments on commit 57a3050

Please sign in to comment.