Skip to content

Commit

Permalink
Add ability for user to place bids (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbowa authored May 8, 2024
2 parents 323120f + 688f1a9 commit 23c3945
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 58 deletions.
84 changes: 71 additions & 13 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,85 @@
name: test

name: Run Tests
on:
push:
branches: [main]
branches:
- main
pull_request:
branches: [main]

env:
CARGO_TERM_COLOR: always
solana_version: v1.18.8

jobs:
build:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
name: cache solana cli
id: cache-solana
with:
path: |
~/.cache/solana/
~/.local/share/solana/
key: solana-${{ runner.os }}-v0000-${{ env.solana_version }}

- name: install essentials
run: |
sudo apt-get update
sudo apt-get install -y pkg-config build-essential libudev-dev
npm install --global yarn
- name: install rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Cache rust
uses: Swatinem/rust-cache@v2

- name: install solana
if: steps.cache-solana.outputs.cache-hit != 'true'
run: |
sh -c "$(curl -sSfL https://release.solana.com/${{ env.solana_version }}/install)"
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
solana --version
lint:
needs: install
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Install Solana CLI
- name: Run fmt
run: cargo fmt -- --check
- name: Run clippy
run: cargo clippy

test:
needs: [install, lint]
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
name: cache solana cli
id: cache-solana
with:
path: |
~/.cache/solana/
~/.local/share/solana/
key: solana-${{ runner.os }}-v0000-${{ env.solana_version }}

- name: setup solana
run: |
sh -c "$(curl -sSfL https://release.solana.com/v1.18.4/install)"
echo 'export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH"' >> $HOME/.bashrc
- name: Build
export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH"
solana --version
solana-keygen new --silent --no-bip39-passphrase
- name: run build
run: |
cargo build
- name: run tests
run: |
export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH"
cargo build-bpf
- name: Run tests
run: cargo test --verbose
cargo test-sbf
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# TinyBlob
# Pith
4 changes: 3 additions & 1 deletion src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
// The seed for market PDAs.
// The seed for the market PDA.
pub const MARKET: &[u8] = b"market";
// The seed for the bid PDA.
pub const BID: &[u8] = b"bid";
22 changes: 16 additions & 6 deletions src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,28 @@ use shank::ShankInstruction;
pub enum PithInstruction {
#[account(0, name = "signer", desc = "Signer", signer)]
#[account(1, name = "market", desc = "Pith market account", writable)]
#[account(2, name = "system_program", desc = "Solana System Program", writable)]
CreateMarket = 0,
#[account(2, name = "system_program", desc = "Solana System Program")]
Market = 0,

DeleteMarket = 1,

UpdateMarket = 2,
#[account(0, name = "signer", desc = "Signer", signer)]
#[account(1, name = "market", desc = "Pith market account", writable)]
#[account(2, name = "bid", desc = "Bid account", writable)]
#[account(3, name = "system_program", desc = "Solana System Program")]
Bid = 1,
}

#[repr(C)]
#[derive(Clone, Debug, BorshDeserialize)]
pub struct CreateMarketArgs {
pub struct MarketArgs {
pub bump: u8,
pub id: u64,
pub title: String,
}

#[repr(C)]
#[derive(Clone, Debug, BorshDeserialize)]
pub struct BidArgs {
pub bump: u8,
pub id: u64,
pub amount: u64,
}
5 changes: 2 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ pub fn process_instruction(
.ok_or(ProgramError::InvalidInstructionData)?;

match PithInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? {
PithInstruction::CreateMarket => process_init_market(program_id, accounts, data)?,
PithInstruction::DeleteMarket => process_delete_market(program_id, accounts, data)?,
PithInstruction::UpdateMarket => process_update_market(program_id, accounts, data)?,
PithInstruction::Market => process_market(program_id, accounts, data)?,
PithInstruction::Bid => process_bid(program_id, accounts, data)?,
}

Ok(())
Expand Down
9 changes: 5 additions & 4 deletions src/loaders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, system_program,
};

// load_signer throws an error if the account is not the signer.
/// Errors if:
/// The account is not the signer.
pub fn load_signer<'a, 'info>(info: &AccountInfo<'info>) -> Result<(), ProgramError> {
if !info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
Expand Down Expand Up @@ -32,9 +33,9 @@ pub fn load_uninitialized_account<'a, 'info>(
Ok(())
}

// load_uninitialized_pda will throw an error if;
// The keys do not match
// The bump does not match
/// Errors if:
/// The keys do not match
/// The bump does not match
pub fn load_uninitialized_pda<'a, 'info>(
info: &'a AccountInfo<'info>,
seeds: &[&[u8]],
Expand Down
73 changes: 73 additions & 0 deletions src/processor/bid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::AccountInfo, borsh1::try_from_slice_unchecked, entrypoint::ProgramResult, msg,
program_error::ProgramError, pubkey::Pubkey, system_program,
};

use crate::{
instruction::BidArgs,
loaders::{load_program, load_signer, load_uninitialized_pda},
state::{Bid, Market},
utils::create_pda,
BID,
};

/// process_bid handles the creation of a new bid on a market.
pub fn process_bid<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
// Parse args
let args = BidArgs::try_from_slice(data)?;

// Load account data
let [signer, market_info, bid_info, system_program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

let mut market_data = try_from_slice_unchecked::<Market>(&market_info.data.borrow())?;

load_signer(signer)?;
load_uninitialized_pda(
bid_info,
&[
BID,
market_data.id.to_le_bytes().as_ref(),
signer.key.as_ref(),
args.id.to_le_bytes().as_ref(),
],
args.bump,
&crate::id(),
)?;
load_program(system_program, system_program::id())?;

// create bid Program Derived Address.
create_pda(
bid_info,
&crate::id(),
Bid::SIZE,
&[
BID,
market_data.id.to_le_bytes().as_ref(),
signer.key.as_ref(),
args.id.to_le_bytes().as_ref(),
&[args.bump],
],
system_program,
signer,
)?;

let mut bid_data = try_from_slice_unchecked::<Bid>(&bid_info.data.borrow()).unwrap();

bid_data.discriminator = Bid::DISCRIMINATOR.to_string();
bid_data.market = *market_info.key;
bid_data.amount = args.amount;
bid_data.authority = *signer.key;
bid_data.serialize(&mut &mut bid_info.data.borrow_mut()[..])?;

market_data.counter += 1;
market_data.serialize(&mut &mut market_info.data.borrow_mut()[..])?;

Ok(())
}
35 changes: 9 additions & 26 deletions src/processor/market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ use solana_program::{
program_error::ProgramError, pubkey::Pubkey, system_program,
};

use crate::{instruction::CreateMarketArgs, loaders::*, state::Market, utils::*, MARKET};
use crate::{instruction::MarketArgs, loaders::*, state::Market, utils::*, MARKET};
use borsh::{BorshDeserialize, BorshSerialize};

// process_init_market creates a new tradable market.
pub fn process_init_market<'a, 'info>(
// process_market creates a new tradable market.
pub fn process_market<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
// Parse args
let args = CreateMarketArgs::try_from_slice(data)?;
let args = MarketArgs::try_from_slice(data)?;

// Load account data
let [signer, market_info, system_program] = accounts else {
Expand All @@ -33,11 +33,7 @@ pub fn process_init_market<'a, 'info>(
create_pda(
market_info,
&crate::id(),
// Calculate how much space we need.
// 1 byte => bump
// 8 bytes => id
// 4 bytes + title.len() => title
1 + 8 + (4 + args.title.len()),
Market::get_account_size(&args.title, &Market::DISCRIMINATOR.to_string()),
&[
MARKET,
signer.key.as_ref(),
Expand All @@ -49,27 +45,14 @@ pub fn process_init_market<'a, 'info>(
)?;

let mut market_data = try_from_slice_unchecked::<Market>(&market_info.data.borrow()).unwrap();
market_data.discriminator = Market::DISCRIMINATOR.to_string();
market_data.bump = args.bump;
market_data.authority = *signer.key;
market_data.id = args.id;
market_data.title = args.title;

market_data.key = *market_info.key;
market_data.counter = 0;
market_data.serialize(&mut &mut market_info.data.borrow_mut()[..])?;

Ok(())
}

pub fn process_delete_market<'a, 'info>(
_program_id: &Pubkey,
_accounts: &'a [AccountInfo<'info>],
_data: &[u8],
) -> ProgramResult {
todo!()
}

pub fn process_update_market<'a, 'info>(
_program_id: &Pubkey,
_accounts: &'a [AccountInfo<'info>],
_data: &[u8],
) -> ProgramResult {
todo!()
}
2 changes: 2 additions & 0 deletions src/processor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod bid;
mod market;

pub use bid::*;
pub use market::*;
21 changes: 21 additions & 0 deletions src/state/bid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use borsh::{BorshDeserialize, BorshSerialize};
use shank::ShankAccount;
use solana_program::pubkey::Pubkey;

#[repr(C)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, ShankAccount)]
pub struct Bid {
/// Account discriminator.
pub discriminator: String,
/// The market key associated with this bid.
pub market: Pubkey,
/// The amount of the bid in lamports.
pub amount: u64,
/// The account that placed this bid.
pub authority: Pubkey,
}

impl Bid {
pub const DISCRIMINATOR: &'static str = "bid";
pub const SIZE: usize = (4 + Bid::DISCRIMINATOR.len()) + 32 + 8 + 32;
}
28 changes: 24 additions & 4 deletions src/state/market.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
use borsh::{BorshDeserialize, BorshSerialize};
use shank::ShankAccount;
use solana_program::pubkey::Pubkey;

// Market is an account that tracks the current state of the market.
/// Market is the parent account that stores a tradable asset and keeps track of
/// the bids placed on the specific market via a counter.
#[repr(C)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, ShankAccount)]
pub struct Market {
// The proof PDAs bump.
/// Account discriminator
pub discriminator: String,
/// The market account PDA.
pub bump: u8,
// Transaction ID used to keep track of client state.
/// The accounts authority.
pub authority: Pubkey,
/// The unique market ID.
pub id: u64,
// A none-unique string used to identify a market.
/// The title string for a specific market.
pub title: String,
/// Counter keeps track of the number of bids placed on this market.
pub counter: u16,
/// The market account key. Useful since `getMultipleAccountsInfo` does not
/// return a `keyedAccountInfo`.
pub key: Pubkey,
}

impl Market {
pub const DISCRIMINATOR: &'static str = "market";

pub fn get_account_size(title: &String, discriminator: &String) -> usize {
return (4 + discriminator.len()) + 1 + 32 + 8 + (4 + title.len()) + 2 + 32;
}
}
2 changes: 2 additions & 0 deletions src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod bid;
mod market;

pub use bid::*;
pub use market::*;

0 comments on commit 23c3945

Please sign in to comment.