Skip to content

Commit

Permalink
Find send (#6)
Browse files Browse the repository at this point in the history
* fmt

* move prize to find

* add number validation bacl

* find fails if PDA passed in not init

* working for now

* add lock_buy to prevent someone from creating winning PDA before find

* I think it works now

* more comments and fmt

* rm unused errors

* revert to winning

* update readme
  • Loading branch information
jackrieck authored Feb 15, 2022
1 parent bcea5ca commit 2c92c31
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 89 deletions.
19 changes: 6 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,15 @@ No-loss lottery: Build a platform for users to deposit a variety of tokens into

- users choose 6 numbers, creates PDA numbers and vault pubkey as seed
- users calls `buy` adds in their PDA, receives ticket
- cranks call `draw`, draw selects 6 random numbers and sets these in vault manager config
- cranks call `find`, pass in PDA derived from 6 random numbers generated by `draw`
- if `find` doesnt error, program end lottery and write winner pubkey to vault manager account. Users can redeem their tickets for their tokens
- if `find` errors, it means there is no PDA with that seed, there is no winner
- cranks call `draw`, draw selects 6 random numbers and sets these in vault manager config. `draw` locks `buy` until find is called
- cranks call `find`, pass in PDA derived from winning numbers generated by `draw`
- if winning numbers PDA passed to `find` is an already initialized account, send the prize to the owner
- if winning numbers PDA passed to `find` is not initialized, unlock buy, zero out winning numbers, no error

### TODO

- VRF to pick a random winning ticket
- close accounts after
- draw cadence instead of draw time
- what happens if winner never claims?
- buy and redeem multiple tickets at once

#### Solve

- Automate staking
- Automate fees for us
- VRF
- How to make the tickets transferrable?
- stake
- emit event if no winner for the front end
138 changes: 71 additions & 67 deletions programs/no-loss-lottery/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ pub mod no_loss_lottery {
_ticket_bump: u8,
numbers: [u8; 6],
) -> ProgramResult {
// do not allow user to pass in zeroed array of numbers
if numbers == [0u8; 6] {
return Err(ErrorCode::InvalidNumbers.into());
}

// if buy is locked this means someone needs to call find
if ctx.accounts.vault_manager.lock_buy {
return Err(ErrorCode::CallFind.into());
}

// create ticket PDA data
let ticket_account = &mut ctx.accounts.ticket;
ticket_account.mint = ctx.accounts.mint.clone().key();
Expand Down Expand Up @@ -92,11 +102,6 @@ pub mod no_loss_lottery {
_ticket_bump: u8,
_prize_bump: u8,
) -> ProgramResult {
// if lottery is still running, you cannot redeem
if !ctx.accounts.vault_manager.lottery_ended {
return Err(ErrorCode::LotteryInProgress.into());
};

// burn a ticket from the user ATA
let burn_accounts = token::Burn {
mint: ctx.accounts.tickets.clone().to_account_info(),
Expand All @@ -119,31 +124,6 @@ pub mod no_loss_lottery {
.ticket
.close(ctx.accounts.user.clone().to_account_info())?;

// if winner redeems, give them the prize!
// TODO: mark winner as received prize
if ctx.accounts.vault_manager.winner == ctx.accounts.user.key() {
let prize_transfer_accounts = token::Transfer {
from: ctx.accounts.prize.clone().to_account_info(),
to: ctx.accounts.user_ata.clone().to_account_info(),
authority: ctx.accounts.vault_manager.clone().to_account_info(),
};

// transfer prize from vault to winner
// TODO how to get all of prize?
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.clone().to_account_info(),
prize_transfer_accounts,
&[&[
ctx.accounts.mint.key().as_ref(),
ctx.accounts.vault.key().as_ref(),
&[vault_mgr_bump],
]],
),
1,
)?;
};

let transfer_accounts = token::Transfer {
from: ctx.accounts.vault.clone().to_account_info(),
to: ctx.accounts.user_ata.clone().to_account_info(),
Expand Down Expand Up @@ -184,37 +164,61 @@ pub mod no_loss_lottery {

// set numbers in vault_manager account
ctx.accounts.vault_manager.winning_numbers = numbers;

// lock `buy` function until `find` called
ctx.accounts.vault_manager.lock_buy = true;
Ok(())
}

// check if a winning PDA exists
// force passing in the winning numbers PDA
// if PDA exists, send prize
// if not error
pub fn find(
ctx: Context<Find>,
_vault_bump: u8,
_vault_mgr_bump: u8,
vault_mgr_bump: u8,
_tickets_bump: u8,
_numbers: [u8; 6],
_ticket_bump: u8,
) -> ProgramResult {
// check if winning PDA exists
let winning_numbers = ctx.accounts.vault_manager.winning_numbers;

// get ticket numbers from PDA passed in
let ticket_numbers = ctx.accounts.ticket.numbers;

// check if the numbers match the winning numbers
for (i, n) in winning_numbers.iter().enumerate() {
if n != &ticket_numbers[i] {
// reset winning_numbers
// reset draw time
return Err(ErrorCode::NoWinner.into());
}
// unlock buy tickets
ctx.accounts.vault_manager.lock_buy = false;

// zero out winning numbers
ctx.accounts.vault_manager.winning_numbers = [0u8; 6];

// if numbers are zeroed out this means this account was initialized in this transaction
// no winner found
if ctx.accounts.ticket.numbers == [0u8; 6] {
// we cannot error here because we need the variables to persist in the vault_manager account
// close newly created account and return SOL to user
// TODO: emit an event for this condition
return ctx
.accounts
.ticket
.close(ctx.accounts.user.to_account_info());
}

// if winner found, end lottery
ctx.accounts.vault_manager.lottery_ended = true;

// set winner as ticket owner
ctx.accounts.vault_manager.winner = ctx.accounts.ticket.owner;
let transfer_accounts = token::Transfer {
from: ctx.accounts.prize.clone().to_account_info(),
to: ctx.accounts.user_ata.clone().to_account_info(),
authority: ctx.accounts.vault_manager.clone().to_account_info(),
};

Ok(())
// transfer prize to winner
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.clone().to_account_info(),
transfer_accounts,
&[&[
ctx.accounts.mint.key().as_ref(),
ctx.accounts.vault.key().as_ref(),
&[vault_mgr_bump],
]],
),
ctx.accounts.prize.amount,
)
}
}

Expand Down Expand Up @@ -288,7 +292,7 @@ pub struct Buy<'info> {

#[account(init,
payer = user,
seeds = [&numbers],
seeds = [&numbers, vault_manager.key().as_ref()],
bump = ticket_bump,
)]
pub ticket: Box<Account<'info, Ticket>>,
Expand Down Expand Up @@ -386,7 +390,7 @@ pub struct Draw<'info> {
}

#[derive(Accounts)]
#[instruction(vault_bump: u8, vault_mgr_bump: u8, tickets_bump: u8)]
#[instruction(vault_bump: u8, vault_mgr_bump: u8, tickets_bump: u8, numbers: [u8; 6], ticket_bump: u8)]
pub struct Find<'info> {
#[account(mut)]
pub mint: Account<'info, token::Mint>,
Expand All @@ -407,11 +411,20 @@ pub struct Find<'info> {
#[account(mut)]
pub tickets: Account<'info, token::Mint>,

#[account(mut)]
#[account(init_if_needed, payer = user, seeds = [&numbers, vault_manager.key().as_ref()], bump = ticket_bump)]
pub ticket: Box<Account<'info, Ticket>>,

#[account(mut, has_one = mint)]
pub prize: Box<Account<'info, token::TokenAccount>>,

#[account(mut)]
pub user: Signer<'info>,

#[account(mut, has_one = mint)]
pub user_ata: Account<'info, token::TokenAccount>,

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

#[account]
Expand All @@ -420,12 +433,10 @@ pub struct VaultManager {
pub mint: Pubkey,
pub vault: Pubkey,
pub tickets: Pubkey,
pub vault_tickets_ata: Pubkey,
pub draw_time: i64, // in ms, lottery end time
pub ticket_price: u64,
pub winning_numbers: [u8; 6],
pub lottery_ended: bool,
pub winner: Pubkey,
pub lock_buy: bool, // lock buy in draw, unlock buy after find
}

#[account]
Expand All @@ -438,21 +449,14 @@ pub struct Ticket {
pub numbers: [u8; 6],
}

#[account]
#[derive(Default)]
pub struct LotteryResult {
pub winner_exists: bool,
pub winner: Pubkey,
}

#[error]
pub enum ErrorCode {
#[msg("TimeRemaining")]
TimeRemaining,

#[msg("NoWinner")]
NoWinner,
#[msg("Must call Find")]
CallFind,

#[msg("Lottery In Progress")]
LotteryInProgress,
#[msg("Invalid Numbers")]
InvalidNumbers,
}
34 changes: 25 additions & 9 deletions tests/no-loss-lottery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe("no-loss-lottery", () => {
);

// lottery draw timestamp (future)
const drawMs = 3 * 1000;
const drawMs = 1 * 1000;
const now = new Date().getTime();
const drawTime = new anchor.BN(new Date(now + drawMs).getTime() / 1000);

Expand Down Expand Up @@ -111,7 +111,7 @@ describe("no-loss-lottery", () => {

// create ticket PDA
const [ticket, ticketBump] = await anchor.web3.PublicKey.findProgramAddress(
[Uint8Array.from(numbers)],
[Uint8Array.from(numbers), vaultMgr.toBuffer()],
program.programId
);

Expand Down Expand Up @@ -164,30 +164,46 @@ describe("no-loss-lottery", () => {
);
console.log("drawTxSig:", drawTxSig);

// mint tokens to prize for testing
await mint.mintTo(prize, mintAuthority.publicKey, [], 100);
console.log(
"minted 100 tokens to prize ata, dont actually do this in prod"
);

// fetch winning numbers
const vaultMgrAccount = await program.account.vaultManager.fetch(vaultMgr);

// create winning ticket PDA
const [winningTicket, winningTicketBump] =
await anchor.web3.PublicKey.findProgramAddress(
[Uint8Array.from(vaultMgrAccount.winningNumbers), vaultMgr.toBuffer()],
program.programId
);

// find winner
const findTxSig = await program.rpc.find(
vaultBump,
vaultMgrBump,
ticketsBump,
vaultMgrAccount.winningNumbers,
winningTicketBump,
{
accounts: {
mint: mint.publicKey,
vault: vault,
vaultManager: vaultMgr,
tickets: tickets,
ticket: ticket,
ticket: winningTicket,
prize: prize,
user: program.provider.wallet.publicKey,
userAta: userAta.address,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: spl.TOKEN_PROGRAM_ID,
},
}
);
console.log("findTxSig:", findTxSig);

// mint tokens to prize for testing
await mint.mintTo(prize, mintAuthority.publicKey, [], 100);
console.log(
"minted 100 tokens to prize ata, dont actually do this in prod"
);

// user redeem tokens + any winnings
const redeemTxSig = await program.rpc.redeem(
vaultBump,
Expand Down

0 comments on commit 2c92c31

Please sign in to comment.