Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Commit

Permalink
fix edit bid and accept bid (#13)
Browse files Browse the repository at this point in the history
fix: transfer the correct bid amount by reading from bid_data account and accept bid ix is called.

feat: if a higher bid is placed in the edit bid, the difference is topped up to the escrow wallet.
  • Loading branch information
jim4067 authored Jan 20, 2024
1 parent 21fba50 commit b53c1ad
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 81 deletions.
6 changes: 3 additions & 3 deletions programs/soundwork-bid/src/instructions/accept_bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ pub struct AcceptBid<'info> {
)]
pub bidding_data_acc: Account<'info, BiddingDataV1>,

/// CHECK: checked when passing from PDA
#[account(mut)]
pub buyer: AccountInfo<'info>,
pub buyer: SystemAccount<'info>,

#[account(mut)]
pub mint: Account<'info, Mint>,
Expand All @@ -45,7 +44,7 @@ pub struct AcceptBid<'info> {

/// CHECK: initialized calling the buy listing CPI
#[account(mut)]
pub buyer_token_acc: AccountInfo<'info>,
pub buyer_token_acc: UncheckedAccount<'info>,

#[account(mut)]
pub asset_manager: Account<'info, AssetManagerV1>,
Expand Down Expand Up @@ -82,6 +81,7 @@ impl<'info> AcceptBid<'info> {
buyer_token_account: self.buyer_token_acc.to_account_info(),
mint: self.mint.to_account_info(),
listing_data: self.listing_data_acc.to_account_info(),
bid_data_acc: self.bidding_data_acc.to_account_info().into(),
token_program: self.token_program.to_account_info(),
associated_token_program: self.associated_token_program.to_account_info(),
system_program: self.system_program.to_account_info(),
Expand Down
39 changes: 35 additions & 4 deletions programs/soundwork-bid/src/instructions/edit_bid.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use anchor_lang::prelude::*;

use crate::state::bid::BiddingDataV1;
use soundwork_list::error::CustomError;
use soundwork_list::{
cpi::accounts::DepositSol, error::CustomError, program::SoundworkList,
state::sol_escrow::SolEscrowWallet,
};

#[derive(Accounts)]
pub struct EditBid<'info> {
Expand All @@ -14,23 +17,51 @@ pub struct EditBid<'info> {
#[account(mut)]
pub bidding_data_acc: Account<'info, BiddingDataV1>,

#[account(mut)]
pub sol_escrow_wallet: Account<'info, SolEscrowWallet>,

pub system_program: Program<'info, System>,
pub soundwork_list: Program<'info, SoundworkList>,
}

pub fn edit_bid_handler(
ctx: Context<EditBid>,
new_expiry: Option<i64>,
new_expires: Option<i64>,
new_lamports: Option<u64>,
) -> Result<()> {
let bidding_data_acc = &mut ctx.accounts.bidding_data_acc;
let mut bidding_data_acc = ctx.accounts.bidding_data_acc.clone();

if let Some(expire_ts) = new_expiry {
if let Some(expire_ts) = new_expires {
bidding_data_acc.expires_ts = expire_ts;
}

if let Some(lamports) = new_lamports {
if lamports > bidding_data_acc.lamports {
let diff = lamports.checked_sub(bidding_data_acc.lamports);
if diff.is_none() {
return Err(error!(CustomError::Overflow));
}

soundwork_list::cpi::deposit_sol(ctx.accounts.update_escrow_diff_ctx(), diff.unwrap())?;

bidding_data_acc.lamports += lamports;
}

bidding_data_acc.lamports = lamports;
}

Ok(())
}

impl<'info> EditBid<'info> {
pub fn update_escrow_diff_ctx(&self) -> CpiContext<'_, '_, '_, 'info, DepositSol<'info>> {
let cpi_program = self.soundwork_list.to_account_info();
let cpi_accounts = DepositSol {
owner: self.bidder.to_account_info(),
sol_escrow_wallet: self.sol_escrow_wallet.to_account_info(),
system_program: self.system_program.to_account_info(),
};

CpiContext::new(cpi_program, cpi_accounts)
}
}
2 changes: 1 addition & 1 deletion programs/soundwork-bid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pub mod soundwork_bid {

pub fn edit_bid(
ctx: Context<EditBid>,
new_lamports: Option<u64>,
new_expires: Option<i64>,
new_lamports: Option<u64>,
) -> Result<()> {
instructions::edit_bid_handler(ctx, new_expires, new_lamports)
}
Expand Down
2 changes: 2 additions & 0 deletions programs/soundwork-list/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pub enum CustomError {
InvalidAuthority,
#[msg("You have already listed this NFT. Consider editing the listing instead")]
NFTAlreadyListed,
#[msg("operation caused an overflow")]
Overflow,
}
19 changes: 13 additions & 6 deletions programs/soundwork-list/src/instructions/buy_listing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ pub struct BuyListing<'info> {

/// CHECK: program account
#[account(mut)]
pub escrow_wallet_as_buyer: Option<AccountInfo<'info>>,
pub escrow_wallet_as_buyer: Option<UncheckedAccount<'info>>,

/// CHECK: address of NFT lister initialized in listing data account
#[account(mut, address = listing_data.owner.key())]
pub og_owner: AccountInfo<'info>,
pub og_owner: SystemAccount<'info>,

#[account(mut)]
pub asset_manager: Account<'info, AssetManagerV1>,
Expand All @@ -50,19 +50,24 @@ pub struct BuyListing<'info> {
#[account(mut, close = og_owner)]
pub listing_data: Account<'info, ListingDataV1>,

/// CHECK: only used when accept bid is called
#[account(mut)]
pub bid_data_acc: Option<UncheckedAccount<'info>>,

pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}

pub fn buy_listing_handler(ctx: Context<BuyListing>) -> Result<()> {
let bid_data_acc = ctx.accounts.bid_data_acc.as_ref();
let escrow_wallet = ctx.accounts.escrow_wallet_as_buyer.as_ref();

// ! derive asset manager seeds
let (_, bump) = Pubkey::find_program_address(&[b"soundwork".as_ref()], ctx.program_id);
let asset_manager_seeds = &[b"soundwork".as_ref(), &[bump]];
let asset_manager_signer = &[&asset_manager_seeds[..]];

let escrow_wallet = ctx.accounts.escrow_wallet_as_buyer.as_ref();

// when escrow account if provided, buyer wants to pay using his escrow,
// else buyer has to use his wallet
// NOTE: we have lamports checks here instead of using constraints because of mutual exclusivity of the
Expand All @@ -72,17 +77,19 @@ pub fn buy_listing_handler(ctx: Context<BuyListing>) -> Result<()> {
return Err(error!(CustomError::InsufficientFunds));
}

// buying at bid price
escrow_wallet
.unwrap()
.sub_lamports(ctx.accounts.listing_data.lamports)?;
.sub_lamports(bid_data_acc.unwrap().lamports())?; //okay to unwrap here.
ctx.accounts
.og_owner
.add_lamports(ctx.accounts.listing_data.lamports)?;
.add_lamports(bid_data_acc.unwrap().lamports())?; // okay to unwrap here
} else {
if ctx.accounts.buyer.lamports() < ctx.accounts.listing_data.lamports {
return Err(error!(CustomError::InsufficientFunds));
}

// buying at full price
transfer_lamports(
&ctx.accounts.buyer,
&ctx.accounts.og_owner,
Expand Down
106 changes: 55 additions & 51 deletions tests/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as anchor from '@coral-xyz/anchor';
import * as anchor from "@coral-xyz/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import { homedir } from 'os';
import { homedir } from "os";
import { readFileSync } from "fs";
import { Program, Wallet } from "@coral-xyz/anchor";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
Expand All @@ -9,91 +9,95 @@ import { SoundworkBid } from "../target/types/soundwork_bid";
import { SoundworkList } from "../target/types/soundwork_list";

export const authority = anchor.AnchorProvider.env().wallet as Wallet;
export const connection = new anchor.web3.Connection(anchor.web3.clusterApiUrl("devnet"));
export const connection = new anchor.web3.Connection(
anchor.web3.clusterApiUrl("devnet")
);
export const provider = new anchor.AnchorProvider(
connection,
authority,
anchor.AnchorProvider.defaultOptions()
connection,
authority,
anchor.AnchorProvider.defaultOptions()
);

const bidProgramId = new PublicKey("CVuoapcC1RG8y1m86eChkXmynPi4FaykDC8jM8soMZ4j");
const bidProgramId = new PublicKey(
"CVuoapcC1RG8y1m86eChkXmynPi4FaykDC8jM8soMZ4j"
);
const bidProgramIdl = JSON.parse(
readFileSync(
"/home/jimii/Documents/crew/market-contracts/target/idl/soundwork_bid.json", "utf8"
)
readFileSync(
"/home/jimii/Documents/crew/market-contracts/target/idl/soundwork_bid.json",
"utf8"
)
);
export const bidProgram = new Program(
bidProgramIdl,
bidProgramId,
provider
) as Program<SoundworkBid>
bidProgramIdl,
bidProgramId,
provider
) as Program<SoundworkBid>;

const listProgramId = new PublicKey("EUmBNHvFqhkA6Uaqv6pDup5ESHKCqoAweQ4kzAMjNZhX");
const listProgramId = new PublicKey(
"EUmBNHvFqhkA6Uaqv6pDup5ESHKCqoAweQ4kzAMjNZhX"
);
const listProgramIdl = JSON.parse(
readFileSync(
"/home/jimii/Documents/crew/market-contracts/target/idl/soundwork_list.json", "utf8"
)
readFileSync(
"/home/jimii/Documents/crew/market-contracts/target/idl/soundwork_list.json",
"utf8"
)
);
export const listProgram = new Program(
listProgramIdl,
listProgramId,
provider
) as Program<SoundworkList>
listProgramIdl,
listProgramId,
provider
) as Program<SoundworkList>;

const KEYPAIR_PATH_ONE = homedir() + "/.config/solana/id.json";
export const signerOneKp = Keypair.fromSecretKey(
Buffer.from(JSON.parse(readFileSync(KEYPAIR_PATH_ONE, "utf-8")))
Buffer.from(JSON.parse(readFileSync(KEYPAIR_PATH_ONE, "utf-8")))
);
export const borrower = new Wallet(signerOneKp);

const KEYPAIR_PATH_TWO = homedir() + "/.config/solana/id-new.json";
export const signerTwoKp = Keypair.fromSecretKey(
Buffer.from(JSON.parse(readFileSync(KEYPAIR_PATH_TWO, "utf-8")))
Buffer.from(JSON.parse(readFileSync(KEYPAIR_PATH_TWO, "utf-8")))
);

// export const nftMint = new PublicKey("5sQTE5rmngYJzUBavyLcJadL2GYKftavE4bE96c8ZD44");
// export const nftMint = new PublicKey("9Uf4cEXKbWQvBKjEv7dxWACs7pWkfbgPjr5LMp4x7yT7");
// export const nftMint = new PublicKey("RQnttzTrNAwc97N6Kpokc9rwEqihKBd5bYq7SNE2Hc8");
export const nftMint = new PublicKey("Ea7tcpxcX2dk5wQu4aQJShRdRe4xzfvKtGdv7PWNnsuY");
// Ea7tcpxcX2dk5wQu4aQJShRdRe4xzfvKtGdv7PWNnsuY
// export const nftMint = new PublicKey("");

/// derive PDAs
// ! list program
// ! list program
export const [assetManager] = PublicKey.findProgramAddressSync(
[Buffer.from("soundwork")],
listProgram.programId
[Buffer.from("soundwork")],
listProgram.programId
);
export const [listingDataAcc] = PublicKey.findProgramAddressSync(
[nftMint.toBuffer(), Buffer.from("ryo")],
listProgram.programId
[nftMint.toBuffer(), Buffer.from("ryo")],
listProgram.programId
);
export function findUserEscrowWallet(user: PublicKey): PublicKey {
const [userEscrowWaller] = PublicKey.findProgramAddressSync(
[user.toBuffer(), Buffer.from("Hitori")],
listProgram.programId
)
return userEscrowWaller;
const [userEscrowWaller] = PublicKey.findProgramAddressSync(
[user.toBuffer(), Buffer.from("Hitori")],
listProgram.programId
);
return userEscrowWaller;
}

// ! bid program
export const [biddingDataAcc] = PublicKey.findProgramAddressSync(
[nftMint.toBuffer(), Buffer.from("Ikuyo")],
bidProgramId
)

[nftMint.toBuffer(), Buffer.from("Ikuyo")],
bidProgramId
);

// Associated Token Accounts
export const signerOneATA = getAssociatedTokenAddressSync(
nftMint,
signerOneKp.publicKey
nftMint,
signerOneKp.publicKey
);

export const signerTwoATA = getAssociatedTokenAddressSync(
nftMint,
signerTwoKp.publicKey
nftMint,
signerTwoKp.publicKey
);

export const vaultTokenAccount = getAssociatedTokenAddressSync(nftMint, assetManager, true);



export const vaultTokenAccount = getAssociatedTokenAddressSync(
nftMint,
assetManager,
true
);
33 changes: 17 additions & 16 deletions tests/soundwork-bid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ describe("SOUNDWORK BID", async () => {
// });

// it("Edits a bid for an NFT!", async () => {
// let ix = await bidProgram.methods
// .editBid(null, new BN(2 * anchor.web3.LAMPORTS_PER_SOL))
// .accounts({
// bidder: signerTwoKp.publicKey,
// biddingDataAcc,
// systemProgram: anchor.web3.SystemProgram.programId,
// })
// .instruction();
// let ix = await bidProgram.methods
// .editBid(null, new BN(10 * anchor.web3.LAMPORTS_PER_SOL))
// .accounts({
// bidder: signerTwoKp.publicKey,
// biddingDataAcc,
// solEscrowWallet: findUserEscrowWallet(signerTwoKp.publicKey),
// soundworkList: listProgram.programId,
// systemProgram: anchor.web3.SystemProgram.programId,
// })
// .instruction();

// let tx = new anchor.web3.Transaction().add(ix);

Expand Down Expand Up @@ -97,15 +99,14 @@ describe("SOUNDWORK BID", async () => {
// })
// .instruction();

// let tx = new anchor.web3.Transaction().add(ix);

// let txHash = await anchor.web3.sendAndConfirmTransaction(
// connection,
// tx,
// [signerOneKp],
// {skipPreflight: true}
// );
// let tx = new anchor.web3.Transaction().add(ix);

// let txHash = await anchor.web3.sendAndConfirmTransaction(
// connection,
// tx,
// [signerOneKp],
// { skipPreflight: true }
// );
// console.log(
// `accept bid tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet`
// );
Expand Down
1 change: 1 addition & 0 deletions tests/soundwork-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ describe("SOUNDWORK LIST", async () => {
// assetManager,
// vaultTokenAccount,
// listingData: listingDataAcc,
// bidDataAcc: null,
// tokenProgram: TOKEN_PROGRAM_ID,
// associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
// systemProgram: anchor.web3.SystemProgram.programId,
Expand Down

0 comments on commit b53c1ad

Please sign in to comment.