1

I'm writing an English auction program that tries to refund the previous bidder if they are outbid.

I store funds from bidding in a PDA called auction, and attempt to send them to the previous highest bidder if they get outbid. I'm getting this error related to the refund and am having a hard time diagnosing it:

failed to send transaction: Transaction simulation failed: Error processing Instruction 0: invalid program argument

Based on a response from another StackExchange question, I try to "sign" the transfer from the auction PDA to the previous bidder using the seeds and bump of the auction PDA account, but clearly something about how I'm doing it is wrong.

I also tried to refund the previous bidder by manually adjusting the lamports (it's commented out in the code below), but that produced the error sum of account balances before and after instruction do not match.

Does anyone know why this is happening? Here is my bidding function:

pub fn place_bid(ctx: Context<PlaceBid>, amount: u64) -> Result<()> {
    let auction = &mut ctx.accounts.auction;
    let current_time = clock::Clock::get()?.unix_timestamp;
    require!(
        current_time >= auction.start_time,
        AuctionError::AuctionNotStarted
    );
    require!(current_time <= auction.end_time, AuctionError::AuctionEnded);
    require!(amount > auction.highest_bid, AuctionError::BidTooLow);

    // Refund the previous highest bidder, if any
    if auction.highest_bidder != Pubkey::default() {
        let refund_amount = auction.highest_bid;

        //attempt 1: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: invalid program argument
        let bump = auction.bump;
        let seeds= &[
            b"auction1".as_ref(),
            auction.nft_mint.as_ref(),
            &[bump],
        ];
        let seeds_slice = &[&seeds[..]];

        let cpi_context = CpiContext::new_with_signer(
            ctx.accounts.system_program.to_account_info(), 
            system_program::Transfer {
                from: auction.to_account_info(),
                to: ctx.accounts.highest_bidder_account.to_account_info(),
            },
            seeds_slice  
        );

        system_program::transfer(cpi_context, refund_amount)?;

        //attempt 2: Error processing Instruction 0: sum of account balances before and after instruction do not match
        // **auction.to_account_info().try_borrow_mut_lamports()? -= refund_amount;
        // **ctx.accounts.highest_bidder_account.to_account_info().try_borrow_mut_lamports()? += refund_amount;
    }

    // Transfer the new bid amount from the bidder to the auction account
    let cpi_accounts = system_program::Transfer {
        from: ctx.accounts.bidder.to_account_info(),
        to: auction.to_account_info(),
    };
    let cpi_program = ctx.accounts.system_program.to_account_info();
    let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
    system_program::transfer(cpi_context, amount)?;

    // Update the auction state
    auction.highest_bid = amount;
    auction.highest_bidder = *ctx.accounts.bidder.key;

    Ok(())
}

Here are how the accounts are defined:

#[derive(Accounts)]
pub struct PlaceBid<'info> {
    #[account(mut)]
    pub auction: Account<'info, Auction>,
    #[account(mut)]
    pub bidder: Signer<'info>,
    #[account(mut)]
    pub bidder_account: SystemAccount<'info>,
    #[account(mut)]
    pub highest_bidder_account: SystemAccount<'info>,
    pub system_program: Program<'info, System>,
}

My whole Rust program and test code is here, for reference. Please let me know if I can clarify anything.

1 Answer 1

2

You can not use system program transfer with a PDA that is owned by your program. Only with accounts that are owned by the system program.

Use this instead:

            auction.sub_lamports(refund_amount)?;
        ctx.accounts
            .highest_bidder_account
            .add_lamports(refund_amount)?;

Change the code like this and the tests pass:

pub fn place_bid(ctx: Context<PlaceBid>, amount: u64) -> Result<()> {
        let auction = &mut ctx.accounts.auction;
        let current_time = clock::Clock::get()?.unix_timestamp;
        require!(
            current_time >= auction.start_time,
            AuctionError::AuctionNotStarted
        );
        require!(current_time <= auction.end_time, AuctionError::AuctionEnded);
        require!(amount > auction.highest_bid, AuctionError::BidTooLow);

        // Transfer the new bid amount from the bidder to the auction account
        let cpi_accounts = system_program::Transfer {
            from: ctx.accounts.bidder.to_account_info(),
            to: auction.to_account_info(),
        };
        let cpi_program = ctx.accounts.system_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
        system_program::transfer(cpi_context, amount)?;

        // Refund the previous highest bidder, if any
        if auction.highest_bidder != Pubkey::default() {
            let refund_amount = auction.highest_bid;

  
            auction.sub_lamports(refund_amount)?;
            ctx.accounts
                .highest_bidder_account
                .add_lamports(refund_amount)?;
        }

        // Update the auction state
        auction.highest_bid = amount;
        auction.highest_bidder = *ctx.accounts.bidder.key;

        Ok(())
    }
2
  • Awesome, that was it, thanks! Just curious about something, if I use your code above but refund before transferring the bid to the auction account, I get this error: "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: sum of account balances before and after instruction do not match" Why should the order matter?
    – Mike
    Commented Apr 4 at 11:26
  • 1
    hmmm true, strange. Maybe all the direct lamport changes need to be after system program transfer cpis. Not sure why though.
    – Jonas H.
    Commented Apr 4 at 11:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.