Binary oracle implementation (#1347)
* oracle pair init * update * updates * progress * update * update * progress * builds * update * progress * update * copy pasta * Refactor and add Instruction serializing/deserializing * Add pack/unpack for Pool struct * Implement InitPool instruction * Add unit test and refactor InitPool instruction * Minor changes * Add using deposing token mint decimals in pass and fail mints * Add Deposit instruction processing * Add test for Deposit instruction * Add Withdraw instruction processing * Add test for Withdraw instruction * Add Decide instruction with test * Changes in Withdraw instruciton and add time travel to Decide instruction test * Fix clippy warning * Fix warning with if operator * Fix clippy warnings * Update libs version and minor fixes * Minor changes * Add user_transfer_authority to withdraw instruction and other minor changes * Fix clippy warns * Change return value after serialization * Update tokio and solana-program-test libs version Co-authored-by: Anatoly Yakovenko <anatoly@solana.com>
This commit is contained in:
parent
d0bf7157cf
commit
cd57d1cf10
|
@ -3694,6 +3694,22 @@ dependencies = [
|
||||||
"spl-token 3.1.0",
|
"spl-token 3.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spl-binary-oracle-pair"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"arrayref",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"proptest",
|
||||||
|
"solana-program",
|
||||||
|
"solana-sdk",
|
||||||
|
"spl-token 3.0.1",
|
||||||
|
"thiserror",
|
||||||
|
"uint",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spl-example-cross-program-invocation"
|
name = "spl-example-cross-program-invocation"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"associated-token-account/program",
|
"associated-token-account/program",
|
||||||
|
"binary-oracle-pair/program",
|
||||||
"examples/rust/cross-program-invocation",
|
"examples/rust/cross-program-invocation",
|
||||||
"examples/rust/custom-heap",
|
"examples/rust/custom-heap",
|
||||||
"examples/rust/logging",
|
"examples/rust/logging",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
Simple Oracle Pair Token
|
||||||
|
|
||||||
|
1. pick a deposit token
|
||||||
|
2. pick the decider's pubkey
|
||||||
|
3. pick the mint term end slot
|
||||||
|
4. pick the decide term end slot, must be after 3
|
||||||
|
|
||||||
|
Each deposit token can mint one `Pass` and one `Fail` token up to
|
||||||
|
the mint term end slot. After the decide term end slot the `Pass`
|
||||||
|
token converts 1:1 with the deposit token if and only if the decider
|
||||||
|
had set `pass` before the end of the decide term, otherwise the `Fail`
|
||||||
|
token converts 1:1 with the deposit token.
|
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "spl-binary-oracle-pair"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Solana Program Library Binary Oracle Pair"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-bpf = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
num-derive = "0.3"
|
||||||
|
num-traits = "0.2"
|
||||||
|
solana-program = "1.5.14"
|
||||||
|
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||||
|
thiserror = "1.0"
|
||||||
|
uint = "0.8"
|
||||||
|
arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
||||||
|
borsh = "0.8.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-program-test = "1.6.1"
|
||||||
|
solana-sdk = "1.5.14"
|
||||||
|
tokio = { version = "1.3.0", features = ["macros"]}
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
|
@ -0,0 +1,2 @@
|
||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
|
@ -0,0 +1,25 @@
|
||||||
|
//! Program entrypoint definitions
|
||||||
|
|
||||||
|
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
|
||||||
|
|
||||||
|
use crate::{error::PoolError, processor};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
|
||||||
|
program_error::PrintProgramError, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint!(process_instruction);
|
||||||
|
fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
if let Err(error) =
|
||||||
|
processor::Processor::process_instruction(program_id, accounts, instruction_data)
|
||||||
|
{
|
||||||
|
// catch the error so we can print it
|
||||||
|
error.print::<PoolError>();
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
//! Error types
|
||||||
|
|
||||||
|
use num_derive::FromPrimitive;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
use solana_program::{
|
||||||
|
decode_error::DecodeError, msg, program_error::PrintProgramError, program_error::ProgramError,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Errors that may be returned by the Binary Oracle Pair program.
|
||||||
|
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||||
|
pub enum PoolError {
|
||||||
|
/// Pool account already in use
|
||||||
|
#[error("Pool account already in use")]
|
||||||
|
AlreadyInUse,
|
||||||
|
/// Deposit account already in use
|
||||||
|
#[error("Deposit account already in use")]
|
||||||
|
DepositAccountInUse,
|
||||||
|
/// Token mint account already in use
|
||||||
|
#[error("Token account already in use")]
|
||||||
|
TokenMintInUse,
|
||||||
|
/// Invalid seed or bump_seed was provided
|
||||||
|
#[error("Failed to generate program account because of invalid data")]
|
||||||
|
InvalidAuthorityData,
|
||||||
|
/// Invalid authority account provided
|
||||||
|
#[error("Invalid authority account provided")]
|
||||||
|
InvalidAuthorityAccount,
|
||||||
|
/// Lamport balance below rent-exempt threshold.
|
||||||
|
#[error("Lamport balance below rent-exempt threshold")]
|
||||||
|
NotRentExempt,
|
||||||
|
/// Expected an SPL Token mint
|
||||||
|
#[error("Input token mint account is not valid")]
|
||||||
|
InvalidTokenMint,
|
||||||
|
/// Amount should be more than zero
|
||||||
|
#[error("Amount should be more than zero")]
|
||||||
|
InvalidAmount,
|
||||||
|
/// Wrong decider account
|
||||||
|
#[error("Wrong decider account was sent")]
|
||||||
|
WrongDeciderAccount,
|
||||||
|
/// Signature missing in transaction
|
||||||
|
#[error("Signature missing in transaction")]
|
||||||
|
SignatureMissing,
|
||||||
|
/// Decision was already made for this pool
|
||||||
|
#[error("Decision was already made for this pool")]
|
||||||
|
DecisionAlreadyMade,
|
||||||
|
/// Decision can't be made in current slot
|
||||||
|
#[error("Decision can't be made in current slot")]
|
||||||
|
InvalidSlotForDecision,
|
||||||
|
/// Deposit can't be made in current slot
|
||||||
|
#[error("Deposit can't be made in current slot")]
|
||||||
|
InvalidSlotForDeposit,
|
||||||
|
/// No decision has been made yet
|
||||||
|
#[error("No decision has been made yet")]
|
||||||
|
NoDecisionMadeYet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PoolError> for ProgramError {
|
||||||
|
fn from(e: PoolError) -> Self {
|
||||||
|
ProgramError::Custom(e as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DecodeError<T> for PoolError {
|
||||||
|
fn type_of() -> &'static str {
|
||||||
|
"Binary Oracle Pair Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrintProgramError for PoolError {
|
||||||
|
fn print<E>(&self)
|
||||||
|
where
|
||||||
|
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
PoolError::AlreadyInUse => msg!("Error: Pool account already in use"),
|
||||||
|
PoolError::DepositAccountInUse => msg!("Error: Deposit account already in use"),
|
||||||
|
PoolError::TokenMintInUse => msg!("Error: Token account already in use"),
|
||||||
|
PoolError::InvalidAuthorityData => {
|
||||||
|
msg!("Error: Failed to generate program account because of invalid data")
|
||||||
|
}
|
||||||
|
PoolError::InvalidAuthorityAccount => msg!("Error: Invalid authority account provided"),
|
||||||
|
PoolError::NotRentExempt => msg!("Error: Lamport balance below rent-exempt threshold"),
|
||||||
|
PoolError::InvalidTokenMint => msg!("Error: Input token mint account is not valid"),
|
||||||
|
PoolError::InvalidAmount => msg!("Error: Amount should be more than zero"),
|
||||||
|
PoolError::WrongDeciderAccount => msg!("Error: Wrong decider account was sent"),
|
||||||
|
PoolError::SignatureMissing => msg!("Error: Signature missing in transaction"),
|
||||||
|
PoolError::DecisionAlreadyMade => {
|
||||||
|
msg!("Error: Decision was already made for this pool")
|
||||||
|
}
|
||||||
|
PoolError::InvalidSlotForDecision => {
|
||||||
|
msg!("Error: Decision can't be made in current slot")
|
||||||
|
}
|
||||||
|
PoolError::InvalidSlotForDeposit => msg!("Deposit can't be made in current slot"),
|
||||||
|
PoolError::NoDecisionMadeYet => msg!("Error: No decision has been made yet"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
//! Instruction types
|
||||||
|
|
||||||
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
|
use solana_program::{
|
||||||
|
clock::Slot,
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Initialize arguments for pool
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub struct InitArgs {
|
||||||
|
/// mint end slot
|
||||||
|
pub mint_end_slot: Slot,
|
||||||
|
/// decide end slot
|
||||||
|
pub decide_end_slot: Slot,
|
||||||
|
/// authority nonce
|
||||||
|
pub bump_seed: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instruction definition
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub enum PoolInstruction {
|
||||||
|
/// Initializes a new binary oracle pair pool.
|
||||||
|
///
|
||||||
|
/// 0. `[w]` Pool account.
|
||||||
|
/// 1. `[]` Authority
|
||||||
|
/// 2. `[]` Decider authority
|
||||||
|
/// 3. `[]` Deposit currency SPL Token mint. Must be initialized.
|
||||||
|
/// 4. `[w]` Deposit token account. Should not be initialized
|
||||||
|
/// 5. `[w]` Token Pass mint. Should not be initialized
|
||||||
|
/// 6. `[w]` Token Fail mint. Should not be initialized
|
||||||
|
/// 7. `[]` Rent sysvar
|
||||||
|
/// 8. `[]` Token program id
|
||||||
|
InitPool(InitArgs),
|
||||||
|
|
||||||
|
/// Deposit into the pool.
|
||||||
|
///
|
||||||
|
/// 0. `[]` Pool
|
||||||
|
/// 1. `[]` Authority
|
||||||
|
/// 2. `[s]` User transfer authority
|
||||||
|
/// 3. `[w]` Token SOURCE Account, amount is transferable by pool authority with allowances.
|
||||||
|
/// 4. `[w]` Deposit token account
|
||||||
|
/// 5. `[w]` token_P PASS mint
|
||||||
|
/// 6. `[w]` token_F FAIL mint
|
||||||
|
/// 7. `[w]` token_P DESTINATION Account
|
||||||
|
/// 8. `[w]` token_F DESTINATION Account
|
||||||
|
/// 9. `[]` Sysvar Clock
|
||||||
|
/// 10. `[]` Token program id
|
||||||
|
Deposit(u64),
|
||||||
|
|
||||||
|
/// Withdraw from the pool.
|
||||||
|
/// If current slot is < mint_end slot, 1 Pass AND 1 Fail token convert to 1 deposit
|
||||||
|
/// If current slot is > decide_end_slot slot && decide == Some(true), 1 Pass convert to 1 deposit
|
||||||
|
/// otherwise 1 Fail converts to 1 deposit
|
||||||
|
///
|
||||||
|
/// Pass tokens convert 1:1 to the deposit token iff decision is set to Some(true)
|
||||||
|
/// AND current slot is > decide_end_slot.
|
||||||
|
///
|
||||||
|
/// 0. `[]` Pool
|
||||||
|
/// 1. `[]` Authority
|
||||||
|
/// 2. `[s]` User transfer authority
|
||||||
|
/// 3. `[w]` Pool deposit token account
|
||||||
|
/// 4. `[w]` token_P PASS SOURCE Account
|
||||||
|
/// 5. `[w]` token_F FAIL SOURCE Account
|
||||||
|
/// 6. `[w]` token_P PASS mint
|
||||||
|
/// 7. `[w]` token_F FAIL mint
|
||||||
|
/// 8. `[w]` Deposit DESTINATION Account
|
||||||
|
/// 9. `[]` Sysvar Clock
|
||||||
|
/// 10. `[]` Token program id
|
||||||
|
Withdraw(u64),
|
||||||
|
|
||||||
|
/// Trigger the decision.
|
||||||
|
/// Call only succeeds once and if current slot > mint_end slot AND < decide_end slot
|
||||||
|
/// 0. `[]` Pool
|
||||||
|
/// 1. `[s]` Decider pubkey
|
||||||
|
/// 2. `[]` Sysvar Clock
|
||||||
|
Decide(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `InitPool` instruction
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn init_pool(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
pool: &Pubkey,
|
||||||
|
authority: &Pubkey,
|
||||||
|
decider: &Pubkey,
|
||||||
|
deposit_token_mint: &Pubkey,
|
||||||
|
deposit_account: &Pubkey,
|
||||||
|
token_pass_mint: &Pubkey,
|
||||||
|
token_fail_mint: &Pubkey,
|
||||||
|
token_program_id: &Pubkey,
|
||||||
|
init_args: InitArgs,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let init_data = PoolInstruction::InitPool(init_args);
|
||||||
|
let data = init_data.try_to_vec()?;
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(*pool, false),
|
||||||
|
AccountMeta::new_readonly(*authority, false),
|
||||||
|
AccountMeta::new_readonly(*decider, false),
|
||||||
|
AccountMeta::new_readonly(*deposit_token_mint, false),
|
||||||
|
AccountMeta::new(*deposit_account, false),
|
||||||
|
AccountMeta::new(*token_pass_mint, false),
|
||||||
|
AccountMeta::new(*token_fail_mint, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||||
|
AccountMeta::new_readonly(*token_program_id, false),
|
||||||
|
];
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: *program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `Deposit` instruction
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn deposit(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
pool: &Pubkey,
|
||||||
|
authority: &Pubkey,
|
||||||
|
user_transfer_authority: &Pubkey,
|
||||||
|
user_token_account: &Pubkey,
|
||||||
|
pool_deposit_token_account: &Pubkey,
|
||||||
|
token_pass_mint: &Pubkey,
|
||||||
|
token_fail_mint: &Pubkey,
|
||||||
|
token_pass_destination_account: &Pubkey,
|
||||||
|
token_fail_destination_account: &Pubkey,
|
||||||
|
token_program_id: &Pubkey,
|
||||||
|
amount: u64,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let init_data = PoolInstruction::Deposit(amount);
|
||||||
|
let data = init_data.try_to_vec()?;
|
||||||
|
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new_readonly(*pool, false),
|
||||||
|
AccountMeta::new_readonly(*authority, false),
|
||||||
|
AccountMeta::new_readonly(
|
||||||
|
*user_transfer_authority,
|
||||||
|
authority != user_transfer_authority,
|
||||||
|
),
|
||||||
|
AccountMeta::new(*user_token_account, false),
|
||||||
|
AccountMeta::new(*pool_deposit_token_account, false),
|
||||||
|
AccountMeta::new(*token_pass_mint, false),
|
||||||
|
AccountMeta::new(*token_fail_mint, false),
|
||||||
|
AccountMeta::new(*token_pass_destination_account, false),
|
||||||
|
AccountMeta::new(*token_fail_destination_account, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
AccountMeta::new_readonly(*token_program_id, false),
|
||||||
|
];
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: *program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `Withdraw` instruction
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn withdraw(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
pool: &Pubkey,
|
||||||
|
authority: &Pubkey,
|
||||||
|
user_transfer_authority: &Pubkey,
|
||||||
|
pool_deposit_token_account: &Pubkey,
|
||||||
|
token_pass_user_account: &Pubkey,
|
||||||
|
token_fail_user_account: &Pubkey,
|
||||||
|
token_pass_mint: &Pubkey,
|
||||||
|
token_fail_mint: &Pubkey,
|
||||||
|
user_token_destination_account: &Pubkey,
|
||||||
|
token_program_id: &Pubkey,
|
||||||
|
amount: u64,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let init_data = PoolInstruction::Withdraw(amount);
|
||||||
|
let data = init_data.try_to_vec()?;
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new_readonly(*pool, false),
|
||||||
|
AccountMeta::new_readonly(*authority, false),
|
||||||
|
AccountMeta::new_readonly(
|
||||||
|
*user_transfer_authority,
|
||||||
|
authority != user_transfer_authority,
|
||||||
|
),
|
||||||
|
AccountMeta::new(*pool_deposit_token_account, false),
|
||||||
|
AccountMeta::new(*token_pass_user_account, false),
|
||||||
|
AccountMeta::new(*token_fail_user_account, false),
|
||||||
|
AccountMeta::new(*token_pass_mint, false),
|
||||||
|
AccountMeta::new(*token_fail_mint, false),
|
||||||
|
AccountMeta::new(*user_token_destination_account, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
AccountMeta::new_readonly(*token_program_id, false),
|
||||||
|
];
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: *program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `Decide` instruction
|
||||||
|
pub fn decide(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
pool: &Pubkey,
|
||||||
|
decider: &Pubkey,
|
||||||
|
decision: bool,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let init_data = PoolInstruction::Decide(decision);
|
||||||
|
let data = init_data.try_to_vec()?;
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(*pool, false),
|
||||||
|
AccountMeta::new_readonly(*decider, true),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
];
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: *program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
//! binary oracle pair
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod instruction;
|
||||||
|
pub mod processor;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no-entrypoint"))]
|
||||||
|
mod entrypoint;
|
||||||
|
|
||||||
|
// Export current sdk types for downstream users building with a different sdk version
|
||||||
|
pub use solana_program;
|
||||||
|
|
||||||
|
// Binary Oracle Pair id
|
||||||
|
solana_program::declare_id!("Fd7btgySsrjuo25CJCj7oE7VPMyezDhnx7pZkj2v69Nk");
|
|
@ -0,0 +1,589 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::PoolError,
|
||||||
|
instruction::PoolInstruction,
|
||||||
|
state::{Decision, Pool, POOL_VERSION},
|
||||||
|
};
|
||||||
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::next_account_info,
|
||||||
|
account_info::AccountInfo,
|
||||||
|
clock::{Clock, Slot},
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
msg,
|
||||||
|
program::{invoke, invoke_signed},
|
||||||
|
program_error::ProgramError,
|
||||||
|
program_pack::{IsInitialized, Pack},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
use spl_token::state::{Account, Mint};
|
||||||
|
|
||||||
|
/// Program state handler.
|
||||||
|
pub struct Processor {}
|
||||||
|
impl Processor {
|
||||||
|
/// Calculates the authority id by generating a program address.
|
||||||
|
pub fn authority_id(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
my_info: &Pubkey,
|
||||||
|
bump_seed: u8,
|
||||||
|
) -> Result<Pubkey, ProgramError> {
|
||||||
|
Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[bump_seed]], program_id)
|
||||||
|
.map_err(|_| PoolError::InvalidAuthorityData.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transfer tokens with authority
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn transfer<'a>(
|
||||||
|
token_program_id: AccountInfo<'a>,
|
||||||
|
source_account: AccountInfo<'a>,
|
||||||
|
destination_account: AccountInfo<'a>,
|
||||||
|
program_authority_account: AccountInfo<'a>,
|
||||||
|
user_authority_account: AccountInfo<'a>,
|
||||||
|
amount: u64,
|
||||||
|
pool_pub_key: &Pubkey,
|
||||||
|
bump_seed: u8,
|
||||||
|
) -> ProgramResult {
|
||||||
|
if program_authority_account.key == user_authority_account.key {
|
||||||
|
let me_bytes = pool_pub_key.to_bytes();
|
||||||
|
let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
|
||||||
|
let signers = &[&authority_signature_seeds[..]];
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&spl_token::instruction::transfer(
|
||||||
|
token_program_id.key,
|
||||||
|
source_account.key,
|
||||||
|
destination_account.key,
|
||||||
|
program_authority_account.key,
|
||||||
|
&[program_authority_account.key],
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_id,
|
||||||
|
program_authority_account,
|
||||||
|
source_account,
|
||||||
|
destination_account,
|
||||||
|
],
|
||||||
|
signers,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
invoke(
|
||||||
|
&spl_token::instruction::transfer(
|
||||||
|
token_program_id.key,
|
||||||
|
source_account.key,
|
||||||
|
destination_account.key,
|
||||||
|
user_authority_account.key,
|
||||||
|
&[user_authority_account.key],
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_id,
|
||||||
|
user_authority_account,
|
||||||
|
source_account,
|
||||||
|
destination_account,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mint tokens
|
||||||
|
pub fn mint<'a>(
|
||||||
|
token_program_id: AccountInfo<'a>,
|
||||||
|
mint_account: AccountInfo<'a>,
|
||||||
|
destination_account: AccountInfo<'a>,
|
||||||
|
authority_account: AccountInfo<'a>,
|
||||||
|
amount: u64,
|
||||||
|
pool_pub_key: &Pubkey,
|
||||||
|
bump_seed: u8,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let me_bytes = pool_pub_key.to_bytes();
|
||||||
|
let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
|
||||||
|
let signers = &[&authority_signature_seeds[..]];
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&spl_token::instruction::mint_to(
|
||||||
|
token_program_id.key,
|
||||||
|
mint_account.key,
|
||||||
|
destination_account.key,
|
||||||
|
authority_account.key,
|
||||||
|
&[authority_account.key],
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_id,
|
||||||
|
mint_account,
|
||||||
|
destination_account,
|
||||||
|
authority_account,
|
||||||
|
],
|
||||||
|
signers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Burn tokens
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn burn<'a>(
|
||||||
|
token_program_id: AccountInfo<'a>,
|
||||||
|
source_account: AccountInfo<'a>,
|
||||||
|
mint_account: AccountInfo<'a>,
|
||||||
|
program_authority_account: AccountInfo<'a>,
|
||||||
|
user_authority_account: AccountInfo<'a>,
|
||||||
|
amount: u64,
|
||||||
|
pool_pub_key: &Pubkey,
|
||||||
|
bump_seed: u8,
|
||||||
|
) -> ProgramResult {
|
||||||
|
if program_authority_account.key == user_authority_account.key {
|
||||||
|
let me_bytes = pool_pub_key.to_bytes();
|
||||||
|
let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
|
||||||
|
let signers = &[&authority_signature_seeds[..]];
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&spl_token::instruction::burn(
|
||||||
|
token_program_id.key,
|
||||||
|
source_account.key,
|
||||||
|
mint_account.key,
|
||||||
|
program_authority_account.key,
|
||||||
|
&[program_authority_account.key],
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_id,
|
||||||
|
program_authority_account,
|
||||||
|
source_account,
|
||||||
|
mint_account,
|
||||||
|
],
|
||||||
|
signers,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
invoke(
|
||||||
|
&spl_token::instruction::burn(
|
||||||
|
token_program_id.key,
|
||||||
|
source_account.key,
|
||||||
|
mint_account.key,
|
||||||
|
user_authority_account.key,
|
||||||
|
&[user_authority_account.key],
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_id,
|
||||||
|
user_authority_account,
|
||||||
|
source_account,
|
||||||
|
mint_account,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the pool
|
||||||
|
pub fn process_init_pool(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
mint_end_slot: Slot,
|
||||||
|
decide_end_slot: Slot,
|
||||||
|
bump_seed: u8,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
let pool_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let authority_info = next_account_info(account_info_iter)?;
|
||||||
|
let decider_info = next_account_info(account_info_iter)?;
|
||||||
|
let deposit_token_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let deposit_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_pass_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_fail_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let rent_info = next_account_info(account_info_iter)?;
|
||||||
|
let rent = &Rent::from_account_info(rent_info)?;
|
||||||
|
let token_program_info = next_account_info(account_info_iter)?;
|
||||||
|
|
||||||
|
let mut pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
|
||||||
|
// Pool account should not be already initialized
|
||||||
|
if pool.is_initialized() {
|
||||||
|
return Err(PoolError::AlreadyInUse.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pool account is rent-exempt
|
||||||
|
if !rent.is_exempt(pool_account_info.lamports(), pool_account_info.data_len()) {
|
||||||
|
return Err(PoolError::NotRentExempt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if deposit token's mint owner is token program
|
||||||
|
if deposit_token_mint_info.owner != token_program_info.key {
|
||||||
|
return Err(PoolError::InvalidTokenMint.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if deposit token mint is initialized
|
||||||
|
let deposit_token_mint = Mint::unpack(&deposit_token_mint_info.data.borrow())?;
|
||||||
|
|
||||||
|
// Check if bump seed is correct
|
||||||
|
let authority = Self::authority_id(program_id, pool_account_info.key, bump_seed)?;
|
||||||
|
if &authority != authority_info.key {
|
||||||
|
return Err(PoolError::InvalidAuthorityAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let deposit_account = Account::unpack_unchecked(&deposit_account_info.data.borrow())?;
|
||||||
|
if deposit_account.is_initialized() {
|
||||||
|
return Err(PoolError::DepositAccountInUse.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let token_pass = Mint::unpack_unchecked(&token_pass_mint_info.data.borrow())?;
|
||||||
|
if token_pass.is_initialized() {
|
||||||
|
return Err(PoolError::TokenMintInUse.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let token_fail = Mint::unpack_unchecked(&token_fail_mint_info.data.borrow())?;
|
||||||
|
if token_fail.is_initialized() {
|
||||||
|
return Err(PoolError::TokenMintInUse.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(
|
||||||
|
&spl_token::instruction::initialize_account(
|
||||||
|
token_program_info.key,
|
||||||
|
deposit_account_info.key,
|
||||||
|
deposit_token_mint_info.key,
|
||||||
|
authority_info.key,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_info.clone(),
|
||||||
|
deposit_account_info.clone(),
|
||||||
|
deposit_token_mint_info.clone(),
|
||||||
|
authority_info.clone(),
|
||||||
|
rent_info.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
invoke(
|
||||||
|
&spl_token::instruction::initialize_mint(
|
||||||
|
&spl_token::id(),
|
||||||
|
token_pass_mint_info.key,
|
||||||
|
authority_info.key,
|
||||||
|
None,
|
||||||
|
deposit_token_mint.decimals,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_info.clone(),
|
||||||
|
token_pass_mint_info.clone(),
|
||||||
|
rent_info.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
invoke(
|
||||||
|
&spl_token::instruction::initialize_mint(
|
||||||
|
&spl_token::id(),
|
||||||
|
token_fail_mint_info.key,
|
||||||
|
authority_info.key,
|
||||||
|
None,
|
||||||
|
deposit_token_mint.decimals,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
&[
|
||||||
|
token_program_info.clone(),
|
||||||
|
token_fail_mint_info.clone(),
|
||||||
|
rent_info.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
pool.version = POOL_VERSION;
|
||||||
|
pool.bump_seed = bump_seed;
|
||||||
|
pool.token_program_id = *token_program_info.key;
|
||||||
|
pool.deposit_account = *deposit_account_info.key;
|
||||||
|
pool.token_pass_mint = *token_pass_mint_info.key;
|
||||||
|
pool.token_fail_mint = *token_fail_mint_info.key;
|
||||||
|
pool.decider = *decider_info.key;
|
||||||
|
pool.mint_end_slot = mint_end_slot;
|
||||||
|
pool.decide_end_slot = decide_end_slot;
|
||||||
|
pool.decision = Decision::Undecided;
|
||||||
|
|
||||||
|
pool.serialize(&mut *pool_account_info.data.borrow_mut())
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process Deposit instruction
|
||||||
|
pub fn process_deposit(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
amount: u64,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
let pool_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let authority_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
||||||
|
let user_token_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let pool_deposit_token_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_pass_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_fail_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_pass_destination_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_fail_destination_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let clock_info = next_account_info(account_info_iter)?;
|
||||||
|
let clock = &Clock::from_account_info(clock_info)?;
|
||||||
|
let token_program_id_info = next_account_info(account_info_iter)?;
|
||||||
|
|
||||||
|
if amount == 0 {
|
||||||
|
return Err(PoolError::InvalidAmount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
|
||||||
|
|
||||||
|
if clock.slot > pool.mint_end_slot {
|
||||||
|
return Err(PoolError::InvalidSlotForDeposit.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let authority_pub_key =
|
||||||
|
Self::authority_id(program_id, pool_account_info.key, pool.bump_seed)?;
|
||||||
|
if *authority_account_info.key != authority_pub_key {
|
||||||
|
return Err(PoolError::InvalidAuthorityAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer deposit tokens from user's account to our deposit account
|
||||||
|
Self::transfer(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
user_token_account_info.clone(),
|
||||||
|
pool_deposit_token_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
user_transfer_authority_info.clone(),
|
||||||
|
amount,
|
||||||
|
pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Mint PASS tokens to user account
|
||||||
|
Self::mint(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
token_pass_mint_info.clone(),
|
||||||
|
token_pass_destination_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
amount,
|
||||||
|
pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
// Mint FAIL tokens to user account
|
||||||
|
Self::mint(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
token_fail_mint_info.clone(),
|
||||||
|
token_fail_destination_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
amount,
|
||||||
|
pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process Withdraw instruction
|
||||||
|
pub fn process_withdraw(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
amount: u64,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
let pool_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let authority_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
||||||
|
let pool_deposit_token_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_pass_user_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_fail_user_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_pass_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let token_fail_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let user_token_destination_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let clock_info = next_account_info(account_info_iter)?;
|
||||||
|
let clock = &Clock::from_account_info(clock_info)?;
|
||||||
|
let token_program_id_info = next_account_info(account_info_iter)?;
|
||||||
|
|
||||||
|
if amount == 0 {
|
||||||
|
return Err(PoolError::InvalidAmount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_pass_token_account = Account::unpack(&token_pass_user_account_info.data.borrow())?;
|
||||||
|
let user_fail_token_account = Account::unpack(&token_fail_user_account_info.data.borrow())?;
|
||||||
|
|
||||||
|
let pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
|
||||||
|
|
||||||
|
let authority_pub_key =
|
||||||
|
Self::authority_id(program_id, pool_account_info.key, pool.bump_seed)?;
|
||||||
|
if *authority_account_info.key != authority_pub_key {
|
||||||
|
return Err(PoolError::InvalidAuthorityAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match pool.decision {
|
||||||
|
Decision::Pass => {
|
||||||
|
// Burn PASS tokens
|
||||||
|
Self::burn(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
token_pass_user_account_info.clone(),
|
||||||
|
token_pass_mint_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
user_transfer_authority_info.clone(),
|
||||||
|
amount,
|
||||||
|
&pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Transfer deposit tokens from pool deposit account to user destination account
|
||||||
|
Self::transfer(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
pool_deposit_token_account_info.clone(),
|
||||||
|
user_token_destination_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
amount,
|
||||||
|
pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Decision::Fail => {
|
||||||
|
// Burn FAIL tokens
|
||||||
|
Self::burn(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
token_fail_user_account_info.clone(),
|
||||||
|
token_fail_mint_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
user_transfer_authority_info.clone(),
|
||||||
|
amount,
|
||||||
|
&pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Transfer deposit tokens from pool deposit account to user destination account
|
||||||
|
Self::transfer(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
pool_deposit_token_account_info.clone(),
|
||||||
|
user_token_destination_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
amount,
|
||||||
|
pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Decision::Undecided => {
|
||||||
|
let current_slot = clock.slot;
|
||||||
|
if current_slot < pool.mint_end_slot || current_slot > pool.decide_end_slot {
|
||||||
|
let possible_withdraw_amount = amount
|
||||||
|
.min(user_pass_token_account.amount)
|
||||||
|
.min(user_fail_token_account.amount);
|
||||||
|
|
||||||
|
// Burn PASS tokens
|
||||||
|
Self::burn(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
token_pass_user_account_info.clone(),
|
||||||
|
token_pass_mint_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
user_transfer_authority_info.clone(),
|
||||||
|
possible_withdraw_amount,
|
||||||
|
&pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Burn FAIL tokens
|
||||||
|
Self::burn(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
token_fail_user_account_info.clone(),
|
||||||
|
token_fail_mint_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
user_transfer_authority_info.clone(),
|
||||||
|
amount,
|
||||||
|
&pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Transfer deposit tokens from pool deposit account to user destination account
|
||||||
|
Self::transfer(
|
||||||
|
token_program_id_info.clone(),
|
||||||
|
pool_deposit_token_account_info.clone(),
|
||||||
|
user_token_destination_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
authority_account_info.clone(),
|
||||||
|
amount,
|
||||||
|
pool_account_info.key,
|
||||||
|
pool.bump_seed,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
return Err(PoolError::NoDecisionMadeYet.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process Decide instruction
|
||||||
|
pub fn process_decide(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
decision: bool,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
let pool_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let decider_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let clock_info = next_account_info(account_info_iter)?;
|
||||||
|
let clock = &Clock::from_account_info(clock_info)?;
|
||||||
|
|
||||||
|
let mut pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
|
||||||
|
|
||||||
|
if *decider_account_info.key != pool.decider {
|
||||||
|
return Err(PoolError::WrongDeciderAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !decider_account_info.is_signer {
|
||||||
|
return Err(PoolError::SignatureMissing.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if pool.decision != Decision::Undecided {
|
||||||
|
return Err(PoolError::DecisionAlreadyMade.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_slot = clock.slot;
|
||||||
|
if current_slot < pool.mint_end_slot || current_slot > pool.decide_end_slot {
|
||||||
|
return Err(PoolError::InvalidSlotForDecision.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.decision = if decision {
|
||||||
|
Decision::Pass
|
||||||
|
} else {
|
||||||
|
Decision::Fail
|
||||||
|
};
|
||||||
|
|
||||||
|
pool.serialize(&mut *pool_account_info.data.borrow_mut())
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes an instruction
|
||||||
|
pub fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
input: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
let instruction = PoolInstruction::try_from_slice(input)?;
|
||||||
|
match instruction {
|
||||||
|
PoolInstruction::InitPool(init_args) => {
|
||||||
|
msg!("Instruction: InitPool");
|
||||||
|
Self::process_init_pool(
|
||||||
|
program_id,
|
||||||
|
accounts,
|
||||||
|
init_args.mint_end_slot,
|
||||||
|
init_args.decide_end_slot,
|
||||||
|
init_args.bump_seed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PoolInstruction::Deposit(amount) => {
|
||||||
|
msg!("Instruction: Deposit");
|
||||||
|
Self::process_deposit(program_id, accounts, amount)
|
||||||
|
}
|
||||||
|
PoolInstruction::Withdraw(amount) => {
|
||||||
|
msg!("Instruction: Withdraw");
|
||||||
|
Self::process_withdraw(program_id, accounts, amount)
|
||||||
|
}
|
||||||
|
PoolInstruction::Decide(decision) => {
|
||||||
|
msg!("Instruction: Decide");
|
||||||
|
Self::process_decide(program_id, accounts, decision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
//! State transition types
|
||||||
|
|
||||||
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// Uninitialized version value, all instances are at least version 1
|
||||||
|
pub const UNINITIALIZED_VERSION: u8 = 0;
|
||||||
|
/// Initialized pool version
|
||||||
|
pub const POOL_VERSION: u8 = 1;
|
||||||
|
|
||||||
|
/// Program states.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub struct Pool {
|
||||||
|
/// Initialized state.
|
||||||
|
pub version: u8,
|
||||||
|
|
||||||
|
/// Nonce used in program address.
|
||||||
|
pub bump_seed: u8,
|
||||||
|
|
||||||
|
/// Program ID of the tokens
|
||||||
|
pub token_program_id: Pubkey,
|
||||||
|
|
||||||
|
/// Account to deposit into
|
||||||
|
pub deposit_account: Pubkey,
|
||||||
|
|
||||||
|
/// Mint information for token Pass
|
||||||
|
pub token_pass_mint: Pubkey,
|
||||||
|
|
||||||
|
/// Mint information for token Fail
|
||||||
|
pub token_fail_mint: Pubkey,
|
||||||
|
|
||||||
|
/// decider key
|
||||||
|
pub decider: Pubkey,
|
||||||
|
|
||||||
|
/// mint end slot
|
||||||
|
pub mint_end_slot: u64,
|
||||||
|
|
||||||
|
/// decide end slot
|
||||||
|
pub decide_end_slot: u64,
|
||||||
|
|
||||||
|
/// decision status
|
||||||
|
pub decision: Decision,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decision status
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug, Clone)]
|
||||||
|
pub enum Decision {
|
||||||
|
/// Decision was not made
|
||||||
|
Undecided,
|
||||||
|
/// Decision set at Pass
|
||||||
|
Pass,
|
||||||
|
/// Decision set at Fail
|
||||||
|
Fail,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pool {
|
||||||
|
/// Length serialized data
|
||||||
|
pub const LEN: usize = 179;
|
||||||
|
|
||||||
|
/// Check if Pool already initialized
|
||||||
|
pub fn is_initialized(&self) -> bool {
|
||||||
|
self.version != UNINITIALIZED_VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
#[cfg(test)]
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_pool_pack_unpack() {
|
||||||
|
let p = Pool {
|
||||||
|
version: 1,
|
||||||
|
bump_seed: 2,
|
||||||
|
token_program_id: Pubkey::new_unique(),
|
||||||
|
deposit_account: Pubkey::new_unique(),
|
||||||
|
token_pass_mint: Pubkey::new_unique(),
|
||||||
|
token_fail_mint: Pubkey::new_unique(),
|
||||||
|
decider: Pubkey::new_unique(),
|
||||||
|
mint_end_slot: 433,
|
||||||
|
decide_end_slot: 5546,
|
||||||
|
decision: Decision::Fail,
|
||||||
|
};
|
||||||
|
|
||||||
|
let packed = p.try_to_vec().unwrap();
|
||||||
|
|
||||||
|
let unpacked = Pool::try_from_slice(packed.as_slice()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(p, unpacked);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue