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

Fix/allow double signer #1059

Merged
merged 4 commits into from
Mar 31, 2023
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
2,109 changes: 2,042 additions & 67 deletions auction-house/js/idl/auction_house.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions auction-house/program/src/bid/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ pub fn bid_logic<'info>(
],
)?;
}

#[allow(clippy::explicit_auto_deref)]
sol_memset(
*ts_info.try_borrow_mut_data()?,
trade_state_bump,
Expand Down Expand Up @@ -954,6 +956,7 @@ pub fn auctioneer_bid_logic<'info>(
],
)?;
}
#[allow(clippy::explicit_auto_deref)]
sol_memset(
*ts_info.try_borrow_mut_data()?,
trade_state_bump,
Expand Down
2 changes: 2 additions & 0 deletions auction-house/program/src/cancel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ fn cancel_logic<'c, 'info>(
.lamports()
.checked_add(curr_lamp)
.ok_or(AuctionHouseError::NumericalOverflow)?;

#[allow(clippy::explicit_auto_deref)]
sol_memset(*trade_state.try_borrow_mut_data()?, 0, TRADE_STATE_SIZE);

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions auction-house/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
//!
//! Full docs can be found [here](https://docs.metaplex.com/auction-house/definition).

#![allow(clippy::result_large_err)]

pub mod auctioneer;
pub mod bid;
pub mod cancel;
Expand Down
6 changes: 1 addition & 5 deletions auction-house/program/src/sell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,7 @@ fn sell_logic<'c, 'info>(
// 1. The wallet being a signer is the only condition in which an NFT can sell at a price of 0.
// If the user does list at 0 then auction house can change the sale price if the 'can_change_sale_price' option is true.
// 2. If the trade is not priced at 0, the wallet holder has to be a signer since auction house cannot sign if listing over 0.
// 3. There must be one and only one signer; there can never be zero signers or two signers.
// 4. Auction house should be the signer for changing the price instead of user wallet for cases when seller lists at 0.
// 3. Auction house should be the signer for changing the price instead of user wallet for cases when seller lists at 0.
if !wallet.to_account_info().is_signer
&& (buyer_price == 0
|| free_seller_trade_state.data_is_empty()
Expand All @@ -407,9 +406,6 @@ fn sell_logic<'c, 'info>(
{
return Err(AuctionHouseError::SaleRequiresSigner.into());
}
if wallet.to_account_info().is_signer && authority.to_account_info().is_signer {
return Err(AuctionHouseError::SaleRequiresExactlyOneSigner.into());
}

let auction_house_key = auction_house.key();

Expand Down
7 changes: 2 additions & 5 deletions auction-house/program/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,11 +713,8 @@ pub fn close_account<'a>(
.checked_add(current_lamports)
.ok_or(AuctionHouseError::NumericalOverflow)?;

sol_memset(
&mut *source_account.try_borrow_mut_data()?,
0,
account_data_size,
);
#[allow(clippy::explicit_auto_deref)]
sol_memset(*source_account.try_borrow_mut_data()?, 0, account_data_size);

Ok(())
}
95 changes: 95 additions & 0 deletions auction-house/program/tests/sell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use solana_sdk::{signer::Signer, sysvar::clock::Clock};
use std::assert_eq;

use solana_program::borsh::try_from_slice_unchecked;

#[tokio::test]
async fn sell_success() {
let mut context = auction_house_program_test().start_with_context().await;
Expand Down Expand Up @@ -344,3 +345,97 @@ async fn auctioneer_sell_no_delegate_fails() {

assert_error!(error, ACCOUNT_NOT_INITIALIZED);
}

#[tokio::test]
async fn multiple_signers() {
let mut context = auction_house_program_test().start_with_context().await;
// Payer Wallet
let (ah, ahkey, ah_auth) = existing_auction_house_test_context(&mut context)
.await
.unwrap();

let test_metadata = Metadata::new();

let owner_pubkey = &test_metadata.token.pubkey();
airdrop(&mut context, owner_pubkey, TEN_SOL).await.unwrap();

airdrop(&mut context, &ah_auth.pubkey(), TEN_SOL)
.await
.unwrap();

airdrop(&mut context, &test_metadata.token.pubkey(), TEN_SOL)
.await
.unwrap();

airdrop(&mut context, &ah.auction_house_fee_account, TEN_SOL)
.await
.unwrap();

context.warp_to_slot(100).unwrap();

test_metadata
.create(
&mut context,
"Test".to_string(),
"TST".to_string(),
"uri".to_string(),
None,
10,
false,
1,
)
.await
.unwrap();
let ((acc, listing_receipt_acc), sell_tx) = sell_multiple_signers(
&mut context,
&ahkey,
&ah,
&test_metadata,
&test_metadata.token,
ah_auth,
1,
1,
);

context
.banks_client
.process_transaction(sell_tx)
.await
.unwrap();

let sts = context
.banks_client
.get_account(acc.seller_trade_state)
.await
.expect("Error Getting Trade State")
.expect("Trade State Empty");
assert_eq!(sts.data.len(), 1);

let timestamp = context
.banks_client
.get_sysvar::<Clock>()
.await
.unwrap()
.unix_timestamp;

let listing_receipt_account = context
.banks_client
.get_account(listing_receipt_acc.receipt)
.await
.expect("getting listing receipt")
.expect("empty listing receipt data");

let listing_receipt =
ListingReceipt::try_deserialize(&mut listing_receipt_account.data.as_ref()).unwrap();

assert_eq!(listing_receipt.auction_house, acc.auction_house);
assert_eq!(listing_receipt.metadata, acc.metadata);
assert_eq!(listing_receipt.seller, acc.wallet);
assert_eq!(listing_receipt.created_at, timestamp);
assert_eq!(listing_receipt.purchase_receipt, None);
assert_eq!(listing_receipt.canceled_at, None);
assert_eq!(listing_receipt.bookkeeper, *owner_pubkey);
assert_eq!(listing_receipt.seller, *owner_pubkey);
assert_eq!(listing_receipt.price, 1);
assert_eq!(listing_receipt.token_size, 1);
}
109 changes: 108 additions & 1 deletion auction-house/program/tests/utils/setup_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,110 @@ pub fn sell(
)
}

pub fn sell_multiple_signers(
context: &mut ProgramTestContext,
ahkey: &Pubkey,
ah: &AuctionHouse,
test_metadata: &Metadata,
signer: &Keypair,
authority: Keypair,
sale_price: u64,
token_size: u64,
) -> (
(
mpl_auction_house::accounts::Sell,
mpl_auction_house::accounts::PrintListingReceipt,
),
Transaction,
) {
let program_id = mpl_auction_house::id();

let token = get_associated_token_address(&signer.pubkey(), &test_metadata.mint.pubkey());

let (seller_trade_state, sts_bump) = find_trade_state_address(
&signer.pubkey(),
ahkey,
&token,
&ah.treasury_mint,
&test_metadata.mint.pubkey(),
sale_price,
token_size,
);
let (listing_receipt, receipt_bump) = find_listing_receipt_address(&seller_trade_state);

let (free_seller_trade_state, free_sts_bump) = find_trade_state_address(
&signer.pubkey(),
ahkey,
&token,
&ah.treasury_mint,
&test_metadata.mint.pubkey(),
0,
token_size,
);
let (pas, pas_bump) = find_program_as_signer_address();

let accounts = mpl_auction_house::accounts::Sell {
wallet: signer.pubkey(),
token_account: token,
metadata: test_metadata.pubkey,
authority: ah.authority,
auction_house: *ahkey,
auction_house_fee_account: ah.auction_house_fee_account,
seller_trade_state,
free_seller_trade_state,
token_program: spl_token::id(),
system_program: solana_program::system_program::id(),
program_as_signer: pas,
rent: sysvar::rent::id(),
};
let mut account_metas = accounts.to_account_metas(None);

for account in account_metas.iter_mut() {
if account.pubkey == ah.authority {
account.is_signer = true;
}
}

let data = mpl_auction_house::instruction::Sell {
trade_state_bump: sts_bump,
free_trade_state_bump: free_sts_bump,
program_as_signer_bump: pas_bump,
token_size,
buyer_price: sale_price,
}
.data();

let instruction = Instruction {
program_id,
data,
accounts: account_metas,
};

let listing_receipt_accounts = mpl_auction_house::accounts::PrintListingReceipt {
receipt: listing_receipt,
bookkeeper: signer.pubkey(),
system_program: system_program::id(),
rent: sysvar::rent::id(),
instruction: sysvar::instructions::id(),
};

let print_receipt_instruction = Instruction {
program_id,
data: mpl_auction_house::instruction::PrintListingReceipt { receipt_bump }.data(),
accounts: listing_receipt_accounts.to_account_metas(None),
};

(
(accounts, listing_receipt_accounts),
Transaction::new_signed_with_payer(
&[instruction, print_receipt_instruction],
Some(&signer.pubkey()),
&[signer, &authority],
context.last_blockhash,
),
)
}

pub fn sell_pnft(
context: &mut ProgramTestContext,
ahkey: &Pubkey,
Expand Down Expand Up @@ -1479,8 +1583,10 @@ pub async fn existing_auction_house_test_context(
let t_mint_key = spl_token::native_mint::id();
let tdw_ata = twd_key;
let seller_fee_basis_points: u16 = 100;

let authority = Keypair::new();
airdrop(context, &authority.pubkey(), 10_000_000_000).await?;

// Derive Auction House Key
let (auction_house_address, bump) =
find_auction_house_address(&authority.pubkey(), &t_mint_key);
Expand All @@ -1489,6 +1595,7 @@ pub async fn existing_auction_house_test_context(
// Derive Auction House Treasury Key
let (auction_house_treasury_key, treasury_bump) =
find_auction_house_treasury_address(&auction_house_address);

let auction_house = create_auction_house(
context,
&authority,
Expand Down Expand Up @@ -1554,7 +1661,7 @@ pub async fn create_sale_delegate_rule_set(
.rule_set_pda(ruleset_addr)
.payer(creator.pubkey())
.build(CreateOrUpdateArgs::V1 {
serialized_rule_set: serialized_rule_set,
serialized_rule_set,
})
.unwrap()
.instruction();
Expand Down