fix: распаковать источник из обёртки smart-updown в корень ветки
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[constant]
|
||||
pub const GLOBAL_SEED: &[u8] = b"global";
|
||||
|
||||
#[constant]
|
||||
pub const BET_SEED: &[u8] = b"bet";
|
||||
|
||||
pub const SIDE_UP: u64 = 0;
|
||||
pub const SIDE_DOWN: u64 = 1;
|
||||
|
||||
pub const STATUS_OPEN: u64 = 0;
|
||||
pub const STATUS_PAYOUT_DONE: u64 = 1;
|
||||
pub const STATUS_HOUSE_WON: u64 = 2;
|
||||
|
||||
pub const BPS_DIVISOR: u64 = 10_000;
|
||||
@@ -0,0 +1,34 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[error_code]
|
||||
pub enum SmartUpdownError {
|
||||
#[msg("Operation is paused")]
|
||||
Paused,
|
||||
|
||||
#[msg("Only the admin can perform this action")]
|
||||
NotAdmin,
|
||||
|
||||
#[msg("Only the relayer is allowed to close bets")]
|
||||
NotRelayer,
|
||||
|
||||
#[msg("Amount below the allowed minimum")]
|
||||
AmountBelowMin,
|
||||
|
||||
#[msg("Amount above the allowed maximum")]
|
||||
AmountAboveMax,
|
||||
|
||||
#[msg("Invalid side value")]
|
||||
InvalidSide,
|
||||
|
||||
#[msg("Bet is already finalized")]
|
||||
BetAlreadyFinalized,
|
||||
|
||||
#[msg("Bet cannot be closed before expiry")]
|
||||
NotExpired,
|
||||
|
||||
#[msg("Bet id mismatch")]
|
||||
BetIdMismatch,
|
||||
|
||||
#[msg("Payout exceeds vault solvency")]
|
||||
InsufficientVault,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[event]
|
||||
pub struct BetCreated {
|
||||
pub bet: Address,
|
||||
pub bettor: Address,
|
||||
pub side: u64,
|
||||
pub amount: u64,
|
||||
pub bet_time: i64,
|
||||
pub expire_time: i64,
|
||||
pub entry_price: u64,
|
||||
}
|
||||
|
||||
#[event]
|
||||
pub struct BetClosed {
|
||||
pub bet: Address,
|
||||
pub exit_price: u64,
|
||||
pub user_won: bool,
|
||||
pub payout: u64,
|
||||
pub status: u64,
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
pub mod close_bet;
|
||||
pub mod create_bet;
|
||||
pub mod initialize;
|
||||
pub mod update;
|
||||
|
||||
#[allow(ambiguous_glob_reexports)]
|
||||
pub use close_bet::*;
|
||||
pub use create_bet::*;
|
||||
pub use initialize::*;
|
||||
pub use update::*;
|
||||
@@ -0,0 +1,95 @@
|
||||
use anchor_lang::{prelude::*, Lamports};
|
||||
|
||||
use crate::{
|
||||
constants::{
|
||||
BPS_DIVISOR, BET_SEED, GLOBAL_SEED, SIDE_DOWN, SIDE_UP, STATUS_HOUSE_WON,
|
||||
STATUS_PAYOUT_DONE, STATUS_OPEN,
|
||||
},
|
||||
error::SmartUpdownError,
|
||||
events::BetClosed,
|
||||
state::{Bet, Global},
|
||||
};
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id: u64, exit_price: u64)]
|
||||
pub struct CloseBet {
|
||||
pub relayer: Signer,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [BET_SEED, id.to_le_bytes()],
|
||||
bump
|
||||
)]
|
||||
pub bet: Account<Bet>,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [GLOBAL_SEED],
|
||||
bump
|
||||
)]
|
||||
pub global: Account<Global>,
|
||||
/// CHECK: recipient must be the bet's bettor and is validated in the handler.
|
||||
#[account(mut)]
|
||||
pub bettor: SystemAccount,
|
||||
pub clock: Sysvar<Clock>,
|
||||
}
|
||||
|
||||
pub fn handler(ctx: &mut Context<CloseBet>, id: u64, exit_price: u64) -> Result<()> {
|
||||
require!(
|
||||
anchor_lang::address_eq(ctx.accounts.relayer.address(), &ctx.accounts.global.relayer),
|
||||
SmartUpdownError::NotRelayer
|
||||
);
|
||||
require!(
|
||||
anchor_lang::address_eq(ctx.accounts.bettor.address(), &ctx.accounts.bet.bettor),
|
||||
SmartUpdownError::NotRelayer
|
||||
);
|
||||
require!(
|
||||
ctx.accounts.bet.status == STATUS_OPEN,
|
||||
SmartUpdownError::BetAlreadyFinalized
|
||||
);
|
||||
let now = ctx.accounts.clock.unix_timestamp;
|
||||
require!(now >= ctx.accounts.bet.expire_time, SmartUpdownError::NotExpired);
|
||||
|
||||
let entry = ctx.accounts.bet.entry_price;
|
||||
let side = ctx.accounts.bet.side;
|
||||
let amount = ctx.accounts.bet.amount;
|
||||
|
||||
if exit_price == entry {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let user_won = (side == SIDE_UP && exit_price > entry)
|
||||
|| (side == SIDE_DOWN && exit_price < entry);
|
||||
|
||||
let payout;
|
||||
if user_won {
|
||||
let multiplier = ctx.accounts.global.multiplier_bps;
|
||||
let payout_amount =
|
||||
(amount as u128)
|
||||
.checked_mul(multiplier as u128)
|
||||
.unwrap()
|
||||
.checked_div(BPS_DIVISOR as u128)
|
||||
.unwrap() as u64;
|
||||
require!(
|
||||
ctx.accounts.global.view().lamports() >= payout_amount,
|
||||
SmartUpdownError::InsufficientVault
|
||||
);
|
||||
payout = payout_amount;
|
||||
ctx.accounts.global.sub_lamports(payout)?;
|
||||
ctx.accounts.bettor.add_lamports(payout)?;
|
||||
ctx.accounts.bet.status = STATUS_PAYOUT_DONE;
|
||||
} else {
|
||||
payout = 0;
|
||||
ctx.accounts.bet.status = STATUS_HOUSE_WON;
|
||||
}
|
||||
|
||||
ctx.accounts.bet.exit_price = exit_price;
|
||||
|
||||
emit!(BetClosed {
|
||||
bet: *ctx.accounts.bet.address(),
|
||||
exit_price,
|
||||
user_won,
|
||||
payout,
|
||||
status: ctx.accounts.bet.status,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::{
|
||||
constants::{BET_SEED, GLOBAL_SEED, SIDE_DOWN, SIDE_UP, STATUS_OPEN},
|
||||
error::SmartUpdownError,
|
||||
events::BetCreated,
|
||||
state::{Bet, Global},
|
||||
};
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id: u64, side: u64, amount: u64, entry_price: u64)]
|
||||
pub struct CreateBet {
|
||||
#[account(mut)]
|
||||
pub bettor: Signer,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [GLOBAL_SEED],
|
||||
bump
|
||||
)]
|
||||
pub global: Account<Global>,
|
||||
#[account(
|
||||
init,
|
||||
payer = bettor,
|
||||
seeds = [BET_SEED, id.to_le_bytes()],
|
||||
bump
|
||||
)]
|
||||
pub bet: Account<Bet>,
|
||||
pub clock: Sysvar<Clock>,
|
||||
pub system_program: Program<System>,
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
ctx: &mut Context<CreateBet>,
|
||||
id: u64,
|
||||
side: u64,
|
||||
amount: u64,
|
||||
entry_price: u64,
|
||||
) -> Result<()> {
|
||||
require!(ctx.accounts.global.paused == 0, SmartUpdownError::Paused);
|
||||
require!(side == SIDE_UP || side == SIDE_DOWN, SmartUpdownError::InvalidSide);
|
||||
require!(
|
||||
amount >= ctx.accounts.global.min_amount,
|
||||
SmartUpdownError::AmountBelowMin
|
||||
);
|
||||
require!(
|
||||
amount <= ctx.accounts.global.max_amount,
|
||||
SmartUpdownError::AmountAboveMax
|
||||
);
|
||||
require!(
|
||||
id == ctx.accounts.global.counter,
|
||||
SmartUpdownError::BetIdMismatch
|
||||
);
|
||||
|
||||
let now = ctx.accounts.clock.unix_timestamp;
|
||||
let bettor_address = *ctx.accounts.bettor.address();
|
||||
let bet_address = *ctx.accounts.bet.address();
|
||||
let expiry_seconds = ctx.accounts.global.expiry_seconds;
|
||||
|
||||
{
|
||||
let bet = &mut ctx.accounts.bet;
|
||||
bet.bettor = bettor_address;
|
||||
bet.side = side;
|
||||
bet.amount = amount;
|
||||
bet.bet_time = now;
|
||||
bet.expire_time = now + expiry_seconds;
|
||||
bet.entry_price = entry_price;
|
||||
bet.exit_price = 0;
|
||||
bet.status = STATUS_OPEN;
|
||||
}
|
||||
|
||||
ctx.accounts.global.counter = ctx.accounts.global.counter.checked_add(1).unwrap();
|
||||
|
||||
{
|
||||
let cpi_accounts = system_program::Transfer {
|
||||
from: ctx.accounts.bettor.cpi_handle_mut(),
|
||||
to: ctx.accounts.global.cpi_handle_mut(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(ctx.accounts.system_program.address(), cpi_accounts);
|
||||
system_program::transfer(cpi_ctx, amount)?;
|
||||
}
|
||||
|
||||
emit!(BetCreated {
|
||||
bet: bet_address,
|
||||
bettor: bettor_address,
|
||||
side,
|
||||
amount,
|
||||
bet_time: now,
|
||||
expire_time: now + expiry_seconds,
|
||||
entry_price,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::{constants::GLOBAL_SEED, state::Global};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize {
|
||||
#[account(mut)]
|
||||
pub admin: Signer,
|
||||
#[account(
|
||||
init,
|
||||
payer = admin,
|
||||
seeds = [GLOBAL_SEED],
|
||||
bump
|
||||
)]
|
||||
pub global: Account<Global>,
|
||||
pub system_program: Program<System>,
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
ctx: &mut Context<Initialize>,
|
||||
min_amount: u64,
|
||||
max_amount: u64,
|
||||
multiplier_bps: u64,
|
||||
expiry_seconds: i64,
|
||||
) -> Result<()> {
|
||||
let global = &mut ctx.accounts.global;
|
||||
global.admin = *ctx.accounts.admin.address();
|
||||
global.relayer = *ctx.accounts.admin.address();
|
||||
global.counter = 0;
|
||||
global.min_amount = min_amount;
|
||||
global.max_amount = max_amount;
|
||||
global.multiplier_bps = multiplier_bps;
|
||||
global.paused = 0;
|
||||
global.expiry_seconds = expiry_seconds;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::{
|
||||
constants::GLOBAL_SEED,
|
||||
error::SmartUpdownError,
|
||||
state::Global,
|
||||
};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Update {
|
||||
pub admin: Signer,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [GLOBAL_SEED],
|
||||
bump
|
||||
)]
|
||||
pub global: Account<Global>,
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
ctx: &mut Context<Update>,
|
||||
new_relayer: Address,
|
||||
min_amount: u64,
|
||||
max_amount: u64,
|
||||
multiplier_bps: u64,
|
||||
paused: u64,
|
||||
expiry_seconds: i64,
|
||||
) -> Result<()> {
|
||||
require!(
|
||||
anchor_lang::address_eq(ctx.accounts.admin.address(), &ctx.accounts.global.admin),
|
||||
SmartUpdownError::NotAdmin
|
||||
);
|
||||
let global = &mut ctx.accounts.global;
|
||||
global.relayer = new_relayer;
|
||||
global.min_amount = min_amount;
|
||||
global.max_amount = max_amount;
|
||||
global.multiplier_bps = multiplier_bps;
|
||||
global.paused = paused;
|
||||
global.expiry_seconds = expiry_seconds;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod instructions;
|
||||
pub mod state;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
pub use instructions::*;
|
||||
|
||||
declare_id!("E7Hg55ywRDKRZJ35rDMJGC3YFThQbh6nAK9GDayG4sr3");
|
||||
|
||||
#[program]
|
||||
pub mod smart_updown {
|
||||
use super::*;
|
||||
|
||||
#[discrim = 0]
|
||||
pub fn initialize(
|
||||
ctx: &mut Context<Initialize>,
|
||||
min_amount: u64,
|
||||
max_amount: u64,
|
||||
multiplier_bps: u64,
|
||||
expiry_seconds: i64,
|
||||
) -> Result<()> {
|
||||
initialize::handler(ctx, min_amount, max_amount, multiplier_bps, expiry_seconds)
|
||||
}
|
||||
|
||||
#[discrim = 1]
|
||||
pub fn create_bet(
|
||||
ctx: &mut Context<CreateBet>,
|
||||
id: u64,
|
||||
side: u64,
|
||||
amount: u64,
|
||||
entry_price: u64,
|
||||
) -> Result<()> {
|
||||
create_bet::handler(ctx, id, side, amount, entry_price)
|
||||
}
|
||||
|
||||
#[discrim = 2]
|
||||
pub fn close_bet(
|
||||
ctx: &mut Context<CloseBet>,
|
||||
id: u64,
|
||||
exit_price: u64,
|
||||
) -> Result<()> {
|
||||
close_bet::handler(ctx, id, exit_price)
|
||||
}
|
||||
|
||||
#[discrim = 3]
|
||||
pub fn update(
|
||||
ctx: &mut Context<Update>,
|
||||
new_relayer: Address,
|
||||
min_amount: u64,
|
||||
max_amount: u64,
|
||||
multiplier_bps: u64,
|
||||
paused: u64,
|
||||
expiry_seconds: i64,
|
||||
) -> Result<()> {
|
||||
update::handler(
|
||||
ctx,
|
||||
new_relayer,
|
||||
min_amount,
|
||||
max_amount,
|
||||
multiplier_bps,
|
||||
paused,
|
||||
expiry_seconds,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[account]
|
||||
pub struct Global {
|
||||
pub counter: u64,
|
||||
pub min_amount: u64,
|
||||
pub max_amount: u64,
|
||||
pub multiplier_bps: u64,
|
||||
pub expiry_seconds: i64,
|
||||
pub paused: u64,
|
||||
pub admin: Address,
|
||||
pub relayer: Address,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Bet {
|
||||
pub amount: u64,
|
||||
pub bet_time: i64,
|
||||
pub expire_time: i64,
|
||||
pub entry_price: u64,
|
||||
pub exit_price: u64,
|
||||
pub side: u64,
|
||||
pub status: u64,
|
||||
pub bettor: Address,
|
||||
}
|
||||
Reference in New Issue
Block a user