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

Add ability for user to place bids #4

Merged
merged 2 commits into from
May 8, 2024
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
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::*;