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

squares #1

Closed
wants to merge 9 commits into from
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ anchor/target/.rustc_info.json
!anchor/target/idl/*.json
!anchor/target/types/*.ts
test-ledger
.yarn
.yarn
.vercel
19 changes: 19 additions & 0 deletions anchor/programs/squares/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "Squares"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "squares"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []

[dependencies]
anchor-lang = "0.28.0"
2 changes: 2 additions & 0 deletions anchor/programs/squares/Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
291 changes: 291 additions & 0 deletions anchor/programs/squares/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::clock::Clock;
use anchor_lang::solana_program::program::invoke;
use anchor_lang::solana_program::pubkey;
use anchor_lang::solana_program::system_instruction;

declare_id!("8B9VcQmn65FYQmWjEvmyEL2D2CiMr2oacoaQstmVuXWV");

#[program]
pub mod football_squares {
use super::*;

pub fn create_game_board(ctx: Context<CreateGameBoard>, cost_per_square: u64) -> Result<()> {
let board = &mut ctx.accounts.board;
board.creator = *ctx.accounts.creator.key;
board.cost_per_square = cost_per_square;
board.status = GameBoardStatus::Open;
board.squares = vec![
GameBoardSquare {
owner: None,
home_team_index: 0,
away_team_index: 0
};
100
];
Ok(())
}

pub fn purchase_square(ctx: Context<PurchaseSquare>, square_indices: Vec<u8>) -> Result<()> {
let board = &mut ctx.accounts.board;
require!(
board.status == GameBoardStatus::Open,
ErrorCode::GameBoardClosed
);

let total_cost = board.cost_per_square * square_indices.len() as u64;
let fee = (total_cost as f64 * 0.069).round() as u64; // TODO
let amount_to_user = total_cost + fee;

for &square_index in &square_indices {
require!(square_index < 100, ErrorCode::InvalidSquareIndex);
let square = &mut board.squares[square_index as usize];
require!(square.owner.is_none(), ErrorCode::SquareAlreadyPurchased);
square.owner = Some(*ctx.accounts.owner.key);
}

let game_transfer_instruction = system_instruction::transfer(
&ctx.accounts.owner.key(),
&ctx.accounts.board.key(),
amount_to_user,
);
invoke(
&game_transfer_instruction,
&[
ctx.accounts.owner.to_account_info(),
ctx.accounts.board.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;

Ok(())
}

pub fn finalize_board(ctx: Context<FinalizeBoard>) -> Result<()> {
let board = &mut ctx.accounts.board;

require!(
board.status == GameBoardStatus::Open,
ErrorCode::NotAllSquaresPurchased
);
require!(
board.squares.iter().all(|s| s.owner.is_some()),
ErrorCode::NotAllSquaresPurchased
);

// Randomization logic
let clock = Clock::get()?;
board.home_team_indices = generate_random_indices(clock.slot);
board.away_team_indices = generate_random_indices(clock.slot + 1);

board.status = GameBoardStatus::Finalized;
Ok(())
}

pub fn update_event_scores(
ctx: Context<UpdateEventScores>,
period: u8,
home: u8,
away: u8,
) -> Result<()> {
require_keys_eq!(
*ctx.accounts.admin.key,
*ADMIN_PUBKEY,
ErrorCode::Unauthorized
);
let scores = &mut ctx.accounts.scores;

let score_update = Some((home, away));
match period {
1..=4 => scores.period_scores[period as usize - 1] = score_update,
5 => scores.period_scores[4] = score_update,
_ => return Err(ErrorCode::InvalidPeriod.into()),
}

Ok(())
}

pub fn initialize_event_scores(
ctx: Context<InitializeEventScores>,
event_name: String,
home_team_name: String,
away_team_name: String,
) -> Result<()> {
let scores = &mut ctx.accounts.scores;
scores.event_name = event_name;
scores.home_team_name = home_team_name;
scores.away_team_name = away_team_name;
scores.period_scores = [None; 5];
Ok(())
}

pub fn calculate_game_board_winners(ctx: Context<CalculateGameBoardWinners>) -> Result<()> {
let board = &mut ctx.accounts.board;
let scores = &ctx.accounts.scores;

if scores
.period_scores
.iter()
.filter(|score| score.is_some())
.count()
> board
.period_winners
.iter()
.filter(|winner| winner.is_some())
.count()
{
update_period_winners(board, scores);
}

if board
.period_winners
.iter()
.filter(|winner| winner.is_some())
.count()
== 5
{
board.status = GameBoardStatus::GameOver;
}

Ok(())
}
}

#[account]
pub struct GameBoard {
pub creator: Pubkey,
pub cost_per_square: u64,
pub status: GameBoardStatus,
pub squares: Vec<GameBoardSquare>,
pub home_team_indices: Vec<u8>,
pub away_team_indices: Vec<u8>,
pub scores_account: Pubkey,
pub period_winners: [Option<Pubkey>; 5],
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct GameBoardSquare {
pub owner: Option<Pubkey>,
pub home_team_index: u8,
pub away_team_index: u8,
}

#[account]
pub struct EventScores {
pub admin: Pubkey,
pub event_name: String,
pub home_team_name: String,
pub away_team_name: String,
pub period_scores: [Option<(u8, u8)>; 5],
}

#[derive(Accounts)]
pub struct CreateGameBoard<'info> {
#[account(init, payer = creator, space = 8 + 5000)]
pub board: Account<'info, GameBoard>,
#[account(mut)]
pub creator: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct PurchaseSquare<'info> {
#[account(mut)]
pub board: Account<'info, GameBoard>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct FinalizeBoard<'info> {
#[account(mut, has_one = creator)]
pub board: Account<'info, GameBoard>,
#[account(mut)]
pub creator: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct UpdateEventScores<'info> {
#[account(mut, has_one = admin)]
pub scores: Account<'info, EventScores>,
#[account(signer)]
pub admin: AccountInfo<'info>,
}

#[derive(Accounts)]
pub struct InitializeEventScores<'info> {
#[account(init, payer = admin, space = 8 + 1024)]
pub scores: Account<'info, EventScores>,
#[account(mut, signer)]
pub admin: Signer<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct CalculateGameBoardWinners<'info> {
#[account(mut, has_one = creator)]
pub board: Account<'info, GameBoard>,
#[account(mut)]
pub scores: Account<'info, EventScores>,
#[account(mut)]
pub creator: Signer<'info>,
}

#[derive(PartialEq, Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize)]
pub enum GameBoardStatus {
Open,
Closed,
Finalized,
GameOver,
}

#[error_code]
pub enum ErrorCode {
GameBoardClosed,
InvalidSquareIndex,
SquareAlreadyPurchased,
Unauthorized,
InvalidPeriod,
GameBoardNotOpen,
NotAllSquaresPurchased,
GameBoardNotFinalized,
PeriodAlreadyPaid,
}

const ADMIN_PUBKEY: &Pubkey = &pubkey!("3iGJM28vywNTUt5hZgHjri5LEzeRBevE75EbGY7wakJA");

fn generate_random_indices(seed: u64) -> Vec<u8> {
let mut indices: Vec<u8> = (0..10).collect();
let mut rng_seed = seed;
for i in (1..indices.len()).rev() {
rng_seed = xorshift64star(rng_seed);
let j = (rng_seed % (i + 1) as u64) as usize;
indices.swap(i, j);
}
indices
}

fn xorshift64star(mut x: u64) -> u64 {
x ^= x << 12;
x ^= x >> 25;
x ^= x << 27;
x.wrapping_mul(0x2545F4914F6CDD1D)
}

fn update_period_winners(board: &mut Account<GameBoard>, scores: &Account<EventScores>) {
for (period_index, score_option) in scores.period_scores.iter().enumerate() {
if let Some((home, away)) = score_option {
let home_mod = home % 10;
let away_mod = away % 10;

for square in &board.squares {
if square.home_team_index == home_mod && square.away_team_index == away_mod {
board.period_winners[period_index] = square.owner;
break;
}
}
}
}
}
1 change: 1 addition & 0 deletions anchor/src/counter-exports.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Here we export some useful types and functions for interacting with the Anchor program.
import { Cluster, PublicKey } from '@solana/web3.js';
import { Program } from '@coral-xyz/anchor';
import type { Counter } from '../target/types/counter';
import { IDL as CounterIDL } from '../target/types/counter';

Expand Down
1 change: 1 addition & 0 deletions anchor/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './counter-exports';
export * from './squares-exports';
27 changes: 27 additions & 0 deletions anchor/src/squares-exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Here we export some useful types and functions for interacting with the Anchor program.
import { Cluster, PublicKey } from '@solana/web3.js';
import { Program } from '@coral-xyz/anchor';
import type { Squares } from '../target/types/squares';
import { IDL as SquaresIDL } from '../target/types/squares';

// Re-export the generated IDL and type
export { Squares, SquaresIDL };
export type SquaresProgram = Program<Squares>;

// After updating your program ID (e.g. after running `anchor keys sync`) update the value below.
export const SQUARES_PROGRAM_ID = new PublicKey(
'8B9VcQmn65FYQmWjEvmyEL2D2CiMr2oacoaQstmVuXWV'
);

// This is a helper function to get the program ID for the Counter program depending on the cluster.
export function getSquaresProgramId(cluster: Cluster) {
switch (cluster) {
case 'devnet':
case 'testnet':
case 'mainnet-beta':
// You only need to update this if you deploy your program on one of these clusters.
return SQUARES_PROGRAM_ID;
default:
return SQUARES_PROGRAM_ID;
}
}
1 change: 1 addition & 0 deletions anchor/target/idl/squares.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"0.1.0","name":"football_squares","instructions":[{"name":"createGame","accounts":[{"name":"game","isMut":true,"isSigner":true},{"name":"user","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"costPerSquare","type":"u64"}]},{"name":"purchaseSquare","accounts":[{"name":"game","isMut":true,"isSigner":false},{"name":"user","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"squareIndices","type":"bytes"}]},{"name":"finalizeGame","accounts":[{"name":"game","isMut":true,"isSigner":false},{"name":"user","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[]},{"name":"updateScores","accounts":[{"name":"scores","isMut":true,"isSigner":false},{"name":"owner","isMut":false,"isSigner":true}],"args":[{"name":"quarter","type":"u8"},{"name":"home","type":"u8"},{"name":"away","type":"u8"}]}],"accounts":[{"name":"Scores","type":{"kind":"struct","fields":[{"name":"firstQuarterScores","type":{"defined":"QuarterScores"}},{"name":"secondQuarterScores","type":{"defined":"QuarterScores"}},{"name":"thirdQuarterScores","type":{"defined":"QuarterScores"}},{"name":"fourthQuarterScores","type":{"defined":"QuarterScores"}},{"name":"finalScores","type":{"defined":"QuarterScores"}}]}},{"name":"Game","type":{"kind":"struct","fields":[{"name":"owner","type":"publicKey"},{"name":"costPerSquare","type":"u64"},{"name":"gameStatus","type":{"defined":"GameStatus"}},{"name":"squares","type":{"vec":{"defined":"Square"}}},{"name":"homeTeamIndices","type":"bytes"},{"name":"awayTeamIndices","type":"bytes"},{"name":"quarterScores","type":{"array":[{"option":{"defined":"QuarterScores"}},5]}},{"name":"quarterWinners","type":{"array":[{"option":"publicKey"},5]}}]}}],"types":[{"name":"QuarterScores","type":{"kind":"struct","fields":[{"name":"home","type":{"option":"u8"}},{"name":"away","type":{"option":"u8"}}]}},{"name":"Square","type":{"kind":"struct","fields":[{"name":"owner","type":{"option":"publicKey"}},{"name":"homeTeamIndex","type":"u8"},{"name":"awayTeamIndex","type":"u8"}]}},{"name":"GameStatus","type":{"kind":"enum","variants":[{"name":"Open"},{"name":"Closed"},{"name":"Finalized"}]}}],"errors":[{"code":6000,"name":"GameClosed","msg":"The game is not open for purchases."},{"code":6001,"name":"InvalidSquareIndex","msg":"Invalid square index."},{"code":6002,"name":"SquareAlreadyPurchased","msg":"This square has already been purchased."},{"code":6003,"name":"Unauthorized","msg":"Only program owner can update scores."},{"code":6004,"name":"InvalidQuarter","msg":"Should be a number 1-5, where 5 is final score."},{"code":6005,"name":"GameNotOpen","msg":"Game's not open."},{"code":6006,"name":"NotAllSquaresPurchased","msg":"Can't finalize, not all squares are occupied."},{"code":6007,"name":"GameNotFinalized","msg":"Can't distribute winnings, game not finalized."},{"code":6008,"name":"QuarterAlreadyPaid"}]}
2 changes: 2 additions & 0 deletions anchor/target/types/squares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type Squares = {"version":"0.1.0","name":"football_squares","instructions":[{"name":"createGameBoard","accounts":[{"name":"board","isMut":true,"isSigner":true},{"name":"creator","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"costPerSquare","type":"u64"}]},{"name":"purchaseSquare","accounts":[{"name":"board","isMut":true,"isSigner":false},{"name":"owner","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"squareIndices","type":"bytes"}]},{"name":"finalizeBoard","accounts":[{"name":"board","isMut":true,"isSigner":false},{"name":"creator","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[]},{"name":"updateEventScores","accounts":[{"name":"scores","isMut":true,"isSigner":false},{"name":"admin","isMut":false,"isSigner":true}],"args":[{"name":"period","type":"u8"},{"name":"home","type":"u8"},{"name":"away","type":"u8"}]},{"name":"initializeEventScores","accounts":[{"name":"scores","isMut":true,"isSigner":true},{"name":"admin","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"eventName","type":"string"},{"name":"homeTeamName","type":"string"},{"name":"awayTeamName","type":"string"}]},{"name":"calculateGameBoardWinners","accounts":[{"name":"board","isMut":true,"isSigner":false},{"name":"scores","isMut":true,"isSigner":false},{"name":"creator","isMut":true,"isSigner":true}],"args":[]}],"accounts":[{"name":"GameBoard","type":{"kind":"struct","fields":[{"name":"creator","type":"publicKey"},{"name":"costPerSquare","type":"u64"},{"name":"status","type":{"defined":"GameBoardStatus"}},{"name":"squares","type":{"vec":{"defined":"GameBoardSquare"}}},{"name":"homeTeamIndices","type":"bytes"},{"name":"awayTeamIndices","type":"bytes"},{"name":"scoresAccount","type":"publicKey"},{"name":"periodWinners","type":{"array":[{"option":"publicKey"},5]}}]}},{"name":"EventScores","type":{"kind":"struct","fields":[{"name":"admin","type":"publicKey"},{"name":"eventName","type":"string"},{"name":"homeTeamName","type":"string"},{"name":"awayTeamName","type":"string"},{"name":"periodScores","type":{"array":[{"option":{"defined":"(u8,u8)"}},5]}}]}}],"types":[{"name":"GameBoardSquare","type":{"kind":"struct","fields":[{"name":"owner","type":{"option":"publicKey"}},{"name":"homeTeamIndex","type":"u8"},{"name":"awayTeamIndex","type":"u8"}]}},{"name":"GameBoardStatus","type":{"kind":"enum","variants":[{"name":"Open"},{"name":"Closed"},{"name":"Finalized"},{"name":"GameOver"}]}}],"errors":[{"code":6000,"name":"GameBoardClosed"},{"code":6001,"name":"InvalidSquareIndex"},{"code":6002,"name":"SquareAlreadyPurchased"},{"code":6003,"name":"Unauthorized"},{"code":6004,"name":"InvalidPeriod"},{"code":6005,"name":"GameBoardNotOpen"},{"code":6006,"name":"NotAllSquaresPurchased"},{"code":6007,"name":"GameBoardNotFinalized"},{"code":6008,"name":"PeriodAlreadyPaid"}]}
export const IDL: Squares = {"version":"0.1.0","name":"football_squares","instructions":[{"name":"createGameBoard","accounts":[{"name":"board","isMut":true,"isSigner":true},{"name":"creator","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"costPerSquare","type":"u64"}]},{"name":"purchaseSquare","accounts":[{"name":"board","isMut":true,"isSigner":false},{"name":"owner","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"squareIndices","type":"bytes"}]},{"name":"finalizeBoard","accounts":[{"name":"board","isMut":true,"isSigner":false},{"name":"creator","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[]},{"name":"updateEventScores","accounts":[{"name":"scores","isMut":true,"isSigner":false},{"name":"admin","isMut":false,"isSigner":true}],"args":[{"name":"period","type":"u8"},{"name":"home","type":"u8"},{"name":"away","type":"u8"}]},{"name":"initializeEventScores","accounts":[{"name":"scores","isMut":true,"isSigner":true},{"name":"admin","isMut":true,"isSigner":true},{"name":"systemProgram","isMut":false,"isSigner":false}],"args":[{"name":"eventName","type":"string"},{"name":"homeTeamName","type":"string"},{"name":"awayTeamName","type":"string"}]},{"name":"calculateGameBoardWinners","accounts":[{"name":"board","isMut":true,"isSigner":false},{"name":"scores","isMut":true,"isSigner":false},{"name":"creator","isMut":true,"isSigner":true}],"args":[]}],"accounts":[{"name":"GameBoard","type":{"kind":"struct","fields":[{"name":"creator","type":"publicKey"},{"name":"costPerSquare","type":"u64"},{"name":"status","type":{"defined":"GameBoardStatus"}},{"name":"squares","type":{"vec":{"defined":"GameBoardSquare"}}},{"name":"homeTeamIndices","type":"bytes"},{"name":"awayTeamIndices","type":"bytes"},{"name":"scoresAccount","type":"publicKey"},{"name":"periodWinners","type":{"array":[{"option":"publicKey"},5]}}]}},{"name":"EventScores","type":{"kind":"struct","fields":[{"name":"admin","type":"publicKey"},{"name":"eventName","type":"string"},{"name":"homeTeamName","type":"string"},{"name":"awayTeamName","type":"string"},{"name":"periodScores","type":{"array":[{"option":{"defined":"(u8,u8)"}},5]}}]}}],"types":[{"name":"GameBoardSquare","type":{"kind":"struct","fields":[{"name":"owner","type":{"option":"publicKey"}},{"name":"homeTeamIndex","type":"u8"},{"name":"awayTeamIndex","type":"u8"}]}},{"name":"GameBoardStatus","type":{"kind":"enum","variants":[{"name":"Open"},{"name":"Closed"},{"name":"Finalized"},{"name":"GameOver"}]}}],"errors":[{"code":6000,"name":"GameBoardClosed"},{"code":6001,"name":"InvalidSquareIndex"},{"code":6002,"name":"SquareAlreadyPurchased"},{"code":6003,"name":"Unauthorized"},{"code":6004,"name":"InvalidPeriod"},{"code":6005,"name":"GameBoardNotOpen"},{"code":6006,"name":"NotAllSquaresPurchased"},{"code":6007,"name":"GameBoardNotFinalized"},{"code":6008,"name":"PeriodAlreadyPaid"}]}
Loading