diff --git a/programs/soundwork-bid/src/instructions/accept_bid.rs b/programs/soundwork-bid/src/instructions/accept_bid.rs index e2aa7d7..ba35f78 100644 --- a/programs/soundwork-bid/src/instructions/accept_bid.rs +++ b/programs/soundwork-bid/src/instructions/accept_bid.rs @@ -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>, @@ -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>, @@ -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(), diff --git a/programs/soundwork-bid/src/instructions/edit_bid.rs b/programs/soundwork-bid/src/instructions/edit_bid.rs index 0e324d3..cd96ffe 100644 --- a/programs/soundwork-bid/src/instructions/edit_bid.rs +++ b/programs/soundwork-bid/src/instructions/edit_bid.rs @@ -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> { @@ -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, - new_expiry: Option, + new_expires: Option, new_lamports: Option, ) -> 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) + } +} diff --git a/programs/soundwork-bid/src/lib.rs b/programs/soundwork-bid/src/lib.rs index d1a4d9d..52e568d 100644 --- a/programs/soundwork-bid/src/lib.rs +++ b/programs/soundwork-bid/src/lib.rs @@ -18,8 +18,8 @@ pub mod soundwork_bid { pub fn edit_bid( ctx: Context, - new_lamports: Option, new_expires: Option, + new_lamports: Option, ) -> Result<()> { instructions::edit_bid_handler(ctx, new_expires, new_lamports) } diff --git a/programs/soundwork-list/src/error.rs b/programs/soundwork-list/src/error.rs index b1f3ea9..e77d008 100644 --- a/programs/soundwork-list/src/error.rs +++ b/programs/soundwork-list/src/error.rs @@ -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, } diff --git a/programs/soundwork-list/src/instructions/buy_listing.rs b/programs/soundwork-list/src/instructions/buy_listing.rs index b5895c9..38c1b47 100644 --- a/programs/soundwork-list/src/instructions/buy_listing.rs +++ b/programs/soundwork-list/src/instructions/buy_listing.rs @@ -21,11 +21,11 @@ pub struct BuyListing<'info> { /// CHECK: program account #[account(mut)] - pub escrow_wallet_as_buyer: Option>, + pub escrow_wallet_as_buyer: Option>, /// 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>, @@ -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>, + 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) -> 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 @@ -72,17 +77,19 @@ pub fn buy_listing_handler(ctx: Context) -> 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, diff --git a/tests/config.ts b/tests/config.ts index 9d6e13f..f7c74d6 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -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"; @@ -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 + bidProgramIdl, + bidProgramId, + provider +) as Program; -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 + listProgramIdl, + listProgramId, + provider +) as Program; 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 +); diff --git a/tests/soundwork-bid.ts b/tests/soundwork-bid.ts index 3f9e422..bc5d5c9 100644 --- a/tests/soundwork-bid.ts +++ b/tests/soundwork-bid.ts @@ -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); @@ -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` // ); diff --git a/tests/soundwork-list.ts b/tests/soundwork-list.ts index 0352997..5c63c89 100644 --- a/tests/soundwork-list.ts +++ b/tests/soundwork-list.ts @@ -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,