feat: migrate smart-updown contract to SPL-token (фантики)
- create_bet: escrow фантиков с bettor на vault (PDA[global,vault]) через anchor_spl::token - close_bet: выплата фантиков из vault на bettor (CPI с signer_seeds vault) - state/constants: comments про единицы (token units decimals=6), + VAULT_SEED - IDL регенерирован (idl/ + relayer/idl/) с аккаунтами bettor_token/vault_token/token_program - тесты обновлены под токен (10/10 + doctest)
This commit is contained in:
@@ -6,6 +6,11 @@ pub const GLOBAL_SEED: &[u8] = b"global";
|
||||
#[constant]
|
||||
pub const BET_SEED: &[u8] = b"bet";
|
||||
|
||||
/// Vault PDA seed: the token vault lives at PDA([GLOBAL_SEED, VAULT_SEED]).
|
||||
/// It cannot reuse GLOBAL_SEED alone — that PDA address is the `global` state account itself.
|
||||
#[constant]
|
||||
pub const VAULT_SEED: &[u8] = b"vault";
|
||||
|
||||
pub const SIDE_UP: u64 = 0;
|
||||
pub const SIDE_DOWN: u64 = 1;
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{Token, TokenAccount};
|
||||
|
||||
use crate::{
|
||||
constants::{
|
||||
BPS_DIVISOR, BET_SEED, GLOBAL_SEED, SIDE_DOWN, SIDE_UP, STATUS_HOUSE_WON,
|
||||
STATUS_PAYOUT_DONE, STATUS_OPEN,
|
||||
STATUS_PAYOUT_DONE, STATUS_OPEN, VAULT_SEED,
|
||||
},
|
||||
error::SmartUpdownError,
|
||||
events::BetClosed,
|
||||
@@ -27,9 +28,19 @@ pub struct CloseBet<'info> {
|
||||
)]
|
||||
pub global: Account<'info, Global>,
|
||||
/// CHECK: recipient must be the bet's bettor and is validated in the handler.
|
||||
#[account(mut)]
|
||||
pub bettor: SystemAccount<'info>,
|
||||
pub clock: Sysvar<'info, Clock>,
|
||||
/// Program-owned token vault: PDA([GLOBAL_SEED, VAULT_SEED]); payouts are paid out from here.
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [GLOBAL_SEED, VAULT_SEED],
|
||||
bump
|
||||
)]
|
||||
pub vault_token: Account<'info, TokenAccount>,
|
||||
/// Bettor's SPL-token account; the payout is sent here.
|
||||
#[account(mut)]
|
||||
pub bettor_token: Account<'info, TokenAccount>,
|
||||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
pub fn handler(ctx: &mut Context<CloseBet>, id: u64, exit_price: u64) -> Result<()> {
|
||||
@@ -69,17 +80,36 @@ let payout;
|
||||
.checked_div(BPS_DIVISOR as u128)
|
||||
.unwrap() as u64;
|
||||
require!(
|
||||
ctx.accounts.global.get_lamports() >= payout_amount,
|
||||
ctx.accounts.vault_token.amount >= payout_amount,
|
||||
SmartUpdownError::InsufficientVault
|
||||
);
|
||||
payout = payout_amount;
|
||||
ctx.accounts.global.sub_lamports(payout)?;
|
||||
ctx.accounts.bettor.add_lamports(payout)?;
|
||||
{
|
||||
// Pay the bettor: transfer the payout from the vault to the bettor's token account.
|
||||
// The vault is owned by the `global` PDA, so the CPI is signed with its seeds.
|
||||
let cpi_accounts = anchor_spl::token::Transfer {
|
||||
from: ctx.accounts.vault_token.to_account_info(),
|
||||
to: ctx.accounts.bettor_token.to_account_info(),
|
||||
authority: ctx.accounts.vault_token.to_account_info(),
|
||||
};
|
||||
let signer_seeds: &[&[u8]] = &[
|
||||
GLOBAL_SEED,
|
||||
VAULT_SEED,
|
||||
&[ctx.bumps.vault_token],
|
||||
];
|
||||
let vault_signer_seeds = [signer_seeds];
|
||||
let cpi_ctx = CpiContext::new_with_signer(
|
||||
ctx.accounts.token_program.to_account_info(),
|
||||
cpi_accounts,
|
||||
&vault_signer_seeds,
|
||||
);
|
||||
anchor_spl::token::transfer(cpi_ctx, payout)?;
|
||||
}
|
||||
ctx.accounts.bet.status = STATUS_PAYOUT_DONE;
|
||||
} else {
|
||||
payout = 0;
|
||||
ctx.accounts.bet.status = STATUS_HOUSE_WON;
|
||||
}
|
||||
payout = 0;
|
||||
ctx.accounts.bet.status = STATUS_HOUSE_WON;
|
||||
}
|
||||
|
||||
ctx.accounts.bet.exit_price = exit_price;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{Token, TokenAccount};
|
||||
|
||||
use crate::{
|
||||
constants::{BET_SEED, GLOBAL_SEED, SIDE_DOWN, SIDE_UP, STATUS_OPEN},
|
||||
constants::{BET_SEED, GLOBAL_SEED, SIDE_DOWN, SIDE_UP, STATUS_OPEN, VAULT_SEED},
|
||||
error::SmartUpdownError,
|
||||
events::BetCreated,
|
||||
state::{Bet, Global},
|
||||
@@ -28,6 +29,17 @@ pub struct CreateBet<'info> {
|
||||
pub bet: Account<'info, Bet>,
|
||||
pub clock: Sysvar<'info, Clock>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// Bettor's SPL-token account (our "fanti" mint); bet amount is pulled from here.
|
||||
#[account(mut)]
|
||||
pub bettor_token: Account<'info, TokenAccount>,
|
||||
/// Program-owned token vault: PDA([GLOBAL_SEED, VAULT_SEED]); bet amounts are escrowed here.
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [GLOBAL_SEED, VAULT_SEED],
|
||||
bump
|
||||
)]
|
||||
pub vault_token: Account<'info, TokenAccount>,
|
||||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
@@ -72,15 +84,18 @@ pub fn handler(
|
||||
ctx.accounts.global.counter = ctx.accounts.global.counter.checked_add(1).unwrap();
|
||||
|
||||
{
|
||||
let cpi_accounts = anchor_lang::system_program::Transfer {
|
||||
from: ctx.accounts.bettor.to_account_info(),
|
||||
to: ctx.accounts.global.to_account_info(),
|
||||
// Escrow: move the bet amount (token units) from the bettor's account to the vault.
|
||||
// The SPL token program enforces that `bettor_token` is owned by `bettor` (authority).
|
||||
let cpi_accounts = anchor_spl::token::Transfer {
|
||||
from: ctx.accounts.bettor_token.to_account_info(),
|
||||
to: ctx.accounts.vault_token.to_account_info(),
|
||||
authority: ctx.accounts.bettor.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(
|
||||
ctx.accounts.system_program.to_account_info(),
|
||||
ctx.accounts.token_program.to_account_info(),
|
||||
cpi_accounts,
|
||||
);
|
||||
anchor_lang::system_program::transfer(cpi_ctx, amount)?;
|
||||
anchor_spl::token::transfer(cpi_ctx, amount)?;
|
||||
}
|
||||
|
||||
emit!(BetCreated {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
/// Global game state.
|
||||
///
|
||||
/// All money-related fields are denominated in **SPL-token base units**
|
||||
/// (our "fanti" token, decimals = 6), not in lamports:
|
||||
/// `min_amount`/`max_amount` bound a bet amount in token units.
|
||||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct Global {
|
||||
@@ -13,6 +18,7 @@ pub struct Global {
|
||||
pub relayer: Pubkey,
|
||||
}
|
||||
|
||||
/// Open bet. `amount` is in token base units (decimals = 6), not lamports.
|
||||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct Bet {
|
||||
|
||||
Reference in New Issue
Block a user