428 lines
15 KiB
Rust
428 lines
15 KiB
Rust
use std::mem::size_of;
|
|
|
|
use arrayref::{array_ref, array_refs};
|
|
use bytemuck::bytes_of;
|
|
use solana_program::account_info::AccountInfo;
|
|
use solana_program::entrypoint::ProgramResult;
|
|
use solana_program::msg;
|
|
use solana_program::program::{invoke, invoke_signed};
|
|
use solana_program::program_error::ProgramError;
|
|
use solana_program::program_pack::Pack;
|
|
use solana_program::pubkey::Pubkey;
|
|
use solana_program::sysvar::rent::Rent;
|
|
use solana_program::sysvar::Sysvar;
|
|
use spl_token::state::{Account, Mint};
|
|
|
|
use crate::error::{OmegaError, OmegaErrorCode, OmegaResult, SourceFileId};
|
|
use crate::instruction::OmegaInstruction;
|
|
use crate::state::{AccountFlag, DETAILS_BUFFER_LEN, Loadable, MAX_OUTCOMES, OmegaContract};
|
|
|
|
pub struct Processor {}
|
|
|
|
|
|
declare_check_assert_macros!(SourceFileId::Processor);
|
|
|
|
impl Processor {
|
|
fn init_omega_contract(
|
|
program_id: &Pubkey,
|
|
accounts: &[AccountInfo],
|
|
exp_time: u64,
|
|
signer_nonce: u64,
|
|
details: &[u8]
|
|
) -> OmegaResult<()> {
|
|
const NUM_FIXED: usize = 6;
|
|
check_assert!(accounts.len() >= NUM_FIXED + 2 && accounts.len() <= NUM_FIXED + MAX_OUTCOMES)?;
|
|
|
|
let (fixed_accs, outcome_accs) = array_refs![accounts, NUM_FIXED; ..;];
|
|
let [
|
|
omega_contract_acc,
|
|
oracle_acc,
|
|
quote_mint_acc,
|
|
vault_acc,
|
|
signer_acc,
|
|
rent_acc
|
|
] = fixed_accs;
|
|
|
|
let rent = Rent::from_account_info(rent_acc)?;
|
|
|
|
check_assert!(omega_contract_acc.owner == program_id)?;
|
|
check_assert!(rent.is_exempt(omega_contract_acc.lamports(), size_of::<OmegaContract>()))?;
|
|
check_assert!(details.len() <= DETAILS_BUFFER_LEN)?;
|
|
|
|
let mut omega_contract = OmegaContract::load_mut(omega_contract_acc)?;
|
|
|
|
check_assert!(omega_contract.account_flags == 0)?;
|
|
let signer_key = gen_signer_key(signer_nonce, omega_contract_acc.key, program_id)?;
|
|
check_assert!(signer_key == *signer_acc.key)?;
|
|
omega_contract.account_flags = (AccountFlag::Initialized | AccountFlag::OmegaContract).bits();
|
|
omega_contract.oracle = *oracle_acc.key;
|
|
omega_contract.quote_mint = *quote_mint_acc.key;
|
|
omega_contract.exp_time = exp_time;
|
|
omega_contract.vault = *vault_acc.key;
|
|
omega_contract.signer_key = *signer_acc.key;
|
|
omega_contract.signer_nonce = signer_nonce;
|
|
omega_contract.winner = Pubkey::default();
|
|
omega_contract.num_outcomes = outcome_accs.len();
|
|
|
|
let details_buf = &mut omega_contract.details[..details.len()];
|
|
details_buf.copy_from_slice(details);
|
|
|
|
let quote_mint = Mint::unpack("e_mint_acc.try_borrow_data()?)?;
|
|
let vault = Account::unpack(&vault_acc.try_borrow_data()?)?;
|
|
check_assert!(vault.owner == signer_key)?;
|
|
check_assert!(&vault.mint == quote_mint_acc.key)?;
|
|
|
|
for (i, outcome_acc) in outcome_accs.iter().enumerate() {
|
|
let outcome = Mint::unpack(&outcome_acc.try_borrow_data()?)?;
|
|
let authority = outcome.mint_authority.ok_or(OmegaErrorCode::InvalidOutcomeMintAuthority)?;
|
|
check_assert!(*outcome_acc.key != Pubkey::default())?;
|
|
check_assert!(outcome.is_initialized)?;
|
|
check_assert!(authority == signer_key)?;
|
|
check_assert!(outcome.supply == 0)?;
|
|
check_assert!(outcome.decimals == quote_mint.decimals)?;
|
|
omega_contract.outcomes[i] = *outcome_acc.key;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn issue_set(
|
|
program_id: &Pubkey,
|
|
accounts: &[AccountInfo],
|
|
quantity: u64
|
|
) -> OmegaResult<()> {
|
|
const NUM_FIXED: usize = 6;
|
|
let (fixed_accs, outcome_accs) = array_refs![accounts, NUM_FIXED; ..;];
|
|
|
|
let [
|
|
omega_contract_acc,
|
|
user_acc,
|
|
user_quote_acc,
|
|
vault_acc,
|
|
spl_token_program_acc,
|
|
omega_signer_acc,
|
|
] = fixed_accs;
|
|
|
|
|
|
// Transfer quote tokens from the user's token wallet
|
|
let omega_contract = OmegaContract::load(omega_contract_acc)?;
|
|
check_assert!(omega_contract.account_flags == (AccountFlag::Initialized | AccountFlag::OmegaContract).bits())?;
|
|
check_assert!(omega_contract_acc.owner == program_id)?;
|
|
check_assert!(*vault_acc.key == omega_contract.vault)?;
|
|
check_assert!(outcome_accs.len() == 2 * omega_contract.num_outcomes)?;
|
|
check_assert!(user_acc.is_signer)?;
|
|
|
|
let deposit_instruction = spl_token::instruction::transfer(
|
|
spl_token_program_acc.key,
|
|
user_quote_acc.key,
|
|
vault_acc.key,
|
|
user_acc.key,
|
|
&[],
|
|
quantity
|
|
)?;
|
|
let deposit_accs = [user_quote_acc.clone(), vault_acc.clone(), user_acc.clone(), spl_token_program_acc.clone()];
|
|
invoke(&deposit_instruction, &deposit_accs)?;
|
|
|
|
let signer_seeds = gen_signer_seeds(&omega_contract.signer_nonce, omega_contract_acc.key);
|
|
for i in 0..omega_contract.num_outcomes {
|
|
let outcome_mint_acc = &outcome_accs[2 * i];
|
|
let outcome_user_acc = &outcome_accs[2 * i + 1];
|
|
|
|
let mint_instruction = spl_token::instruction::mint_to(
|
|
spl_token_program_acc.key,
|
|
outcome_mint_acc.key,
|
|
outcome_user_acc.key,
|
|
omega_signer_acc.key,
|
|
&[],
|
|
quantity,
|
|
)?;
|
|
|
|
let mint_accs = [
|
|
outcome_mint_acc.clone(),
|
|
outcome_user_acc.clone(),
|
|
omega_signer_acc.clone(),
|
|
spl_token_program_acc.clone()
|
|
];
|
|
invoke_signed(&mint_instruction, &mint_accs, &[&signer_seeds])?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn redeem_set(
|
|
program_id: &Pubkey,
|
|
accounts: &[AccountInfo],
|
|
quantity: u64
|
|
) -> OmegaResult<()> {
|
|
const NUM_FIXED: usize = 6;
|
|
let (fixed_accs, outcome_accs) = array_refs![accounts, NUM_FIXED; ..;];
|
|
|
|
let [
|
|
omega_contract_acc,
|
|
user_acc,
|
|
user_quote_acc,
|
|
vault_acc,
|
|
spl_token_program_acc,
|
|
omega_signer_acc,
|
|
] = fixed_accs;
|
|
|
|
// Transfer outcome tokens for each outcome
|
|
let omega_contract = OmegaContract::load(omega_contract_acc)?;
|
|
check_assert!(omega_contract.account_flags == (AccountFlag::Initialized | AccountFlag::OmegaContract).bits())?;
|
|
check_assert!(omega_contract_acc.owner == program_id)?;
|
|
check_assert!(*vault_acc.key == omega_contract.vault)?;
|
|
check_assert!(outcome_accs.len() == 2 * omega_contract.num_outcomes)?;
|
|
check_assert!(user_acc.is_signer)?;
|
|
|
|
for i in 0..omega_contract.num_outcomes {
|
|
let outcome_mint_acc = &outcome_accs[2 * i];
|
|
let outcome_user_acc = &outcome_accs[2 * i + 1];
|
|
|
|
let burn_instruction = spl_token::instruction::burn(
|
|
spl_token_program_acc.key,
|
|
outcome_user_acc.key,
|
|
outcome_mint_acc.key,
|
|
user_acc.key,
|
|
&[],
|
|
quantity,
|
|
)?;
|
|
|
|
let mint_accs = [
|
|
outcome_user_acc.clone(),
|
|
outcome_mint_acc.clone(),
|
|
user_acc.clone(),
|
|
spl_token_program_acc.clone()
|
|
];
|
|
|
|
invoke(&burn_instruction, &mint_accs)?;
|
|
}
|
|
|
|
let withdraw_instruction = spl_token::instruction::transfer(
|
|
spl_token_program_acc.key,
|
|
vault_acc.key,
|
|
user_quote_acc.key,
|
|
omega_signer_acc.key,
|
|
&[],
|
|
quantity
|
|
)?;
|
|
let withdraw_accs = [
|
|
vault_acc.clone(),
|
|
user_quote_acc.clone(),
|
|
omega_signer_acc.clone(),
|
|
spl_token_program_acc.clone()
|
|
];
|
|
let signer_seeds = gen_signer_seeds(&omega_contract.signer_nonce, omega_contract_acc.key);
|
|
invoke_signed(&withdraw_instruction, &withdraw_accs, &[&signer_seeds])?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn redeem_winner(program_id: &Pubkey, accounts: &[AccountInfo], quantity: u64) -> OmegaResult<()>{
|
|
let accounts = array_ref![accounts, 0, 8];
|
|
let [
|
|
omega_contract_acc,
|
|
user_acc,
|
|
user_quote_acc,
|
|
vault_acc,
|
|
spl_token_program_acc,
|
|
omega_signer_acc,
|
|
winner_mint_acc,
|
|
winner_user_acc
|
|
] = accounts;
|
|
let omega_contract = OmegaContract::load(omega_contract_acc)?;
|
|
check_assert!(omega_contract.account_flags == (AccountFlag::Initialized | AccountFlag::OmegaContract).bits())?;
|
|
check_assert!(omega_contract_acc.owner == program_id)?;
|
|
check_assert!(*vault_acc.key == omega_contract.vault)?;
|
|
check_assert!(user_acc.is_signer)?;
|
|
|
|
check_assert!(omega_contract.winner != Pubkey::default())?;
|
|
check_assert!(*winner_mint_acc.key == omega_contract.winner)?;
|
|
|
|
let burn_instruction = spl_token::instruction::burn(
|
|
spl_token_program_acc.key,
|
|
winner_user_acc.key,
|
|
winner_mint_acc.key,
|
|
user_acc.key,
|
|
&[],
|
|
quantity,
|
|
)?;
|
|
|
|
let mint_accs = [
|
|
winner_user_acc.clone(),
|
|
winner_mint_acc.clone(),
|
|
user_acc.clone(),
|
|
spl_token_program_acc.clone()
|
|
];
|
|
|
|
invoke(&burn_instruction, &mint_accs)?;
|
|
|
|
let withdraw_instruction = spl_token::instruction::transfer(
|
|
spl_token_program_acc.key,
|
|
vault_acc.key,
|
|
user_quote_acc.key,
|
|
omega_signer_acc.key,
|
|
&[],
|
|
quantity
|
|
)?;
|
|
let withdraw_accs = [
|
|
vault_acc.clone(),
|
|
user_quote_acc.clone(),
|
|
omega_signer_acc.clone(),
|
|
spl_token_program_acc.clone()
|
|
];
|
|
let signer_seeds = gen_signer_seeds(&omega_contract.signer_nonce, omega_contract_acc.key);
|
|
invoke_signed(&withdraw_instruction, &withdraw_accs, &[&signer_seeds])?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn resolve(program_id: &Pubkey, accounts: &[AccountInfo]) -> OmegaResult<()> {
|
|
let accounts = array_ref![accounts, 0, 4];
|
|
let [
|
|
omega_contract_acc,
|
|
oracle_acc, // signer
|
|
winner_acc,
|
|
clock_acc
|
|
] = accounts;
|
|
|
|
let mut omega_contract = OmegaContract::load_mut(omega_contract_acc)?;
|
|
check_assert!(omega_contract.account_flags == (AccountFlag::Initialized | AccountFlag::OmegaContract).bits())?;
|
|
check_assert!(omega_contract_acc.owner == program_id)?;
|
|
check_assert!(omega_contract.oracle == *oracle_acc.key)?;
|
|
check_assert!(oracle_acc.is_signer)?;
|
|
let clock = solana_program::clock::Clock::from_account_info(clock_acc)?;
|
|
let curr_time = clock.unix_timestamp as u64;
|
|
|
|
check_assert!(omega_contract.exp_time <= curr_time)?;
|
|
check_assert!(omega_contract.winner == Pubkey::default())?;
|
|
|
|
let winner = *winner_acc.key;
|
|
for i in 0..omega_contract.num_outcomes {
|
|
if winner == omega_contract.outcomes[i] {
|
|
omega_contract.winner = winner;
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
Err(OmegaError::ErrorCode(OmegaErrorCode::InvalidWinner))
|
|
}
|
|
|
|
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
|
|
let instruction = OmegaInstruction::unpack(data).ok_or(ProgramError::InvalidInstructionData)?;
|
|
match instruction {
|
|
OmegaInstruction::InitOmegaContract {
|
|
exp_time, signer_nonce,
|
|
} => {
|
|
msg!("InitOmegaContract");
|
|
let details_buffer = &data[16..];
|
|
Self::init_omega_contract(program_id, accounts, exp_time, signer_nonce, details_buffer)?;
|
|
},
|
|
OmegaInstruction::IssueSet {
|
|
quantity
|
|
} => {
|
|
msg!("IssueSet");
|
|
Self::issue_set(program_id, accounts, quantity)?;
|
|
},
|
|
OmegaInstruction::RedeemSet {
|
|
quantity
|
|
} => {
|
|
msg!("RedeemSet");
|
|
Self::redeem_set(program_id, accounts, quantity)?;
|
|
},
|
|
OmegaInstruction::RedeemWinner {
|
|
quantity
|
|
} => {
|
|
msg!("RedeemWinner");
|
|
Self::redeem_winner(program_id, accounts, quantity)?;
|
|
},
|
|
OmegaInstruction::Resolve => {
|
|
msg!("Resolve");
|
|
Self::resolve(program_id, accounts)?;
|
|
}
|
|
}
|
|
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
fn gen_signer_seeds<'a>(nonce: &'a u64, contract_pk: &'a Pubkey) -> [&'a [u8]; 2] {
|
|
[contract_pk.as_ref(), bytes_of(nonce)]
|
|
}
|
|
|
|
fn gen_signer_key(
|
|
nonce: u64,
|
|
contract_pk: &Pubkey,
|
|
program_id: &Pubkey,
|
|
) -> Result<Pubkey, ProgramError> {
|
|
let seeds = gen_signer_seeds(&nonce, contract_pk);
|
|
Ok(Pubkey::create_program_address(&seeds, program_id)?)
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::mem::size_of;
|
|
|
|
use bytemuck::Pod;
|
|
use solana_program::instruction::Instruction;
|
|
use solana_program::program_error::PrintProgramError;
|
|
use solana_program::rent::Rent;
|
|
use solana_sdk::account::{Account, create_account, create_is_signer_account_infos};
|
|
|
|
use crate::error::OmegaError;
|
|
use crate::instruction::*;
|
|
|
|
use super::*;
|
|
|
|
fn do_process_instruction(
|
|
instruction: Instruction,
|
|
accounts: Vec<&mut Account>,
|
|
) -> ProgramResult {
|
|
let mut meta = instruction
|
|
.accounts
|
|
.iter()
|
|
.zip(accounts)
|
|
.map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
|
|
.collect::<Vec<_>>();
|
|
|
|
let account_infos = create_is_signer_account_infos(&mut meta);
|
|
Processor::process(&instruction.program_id, &account_infos, &instruction.data)
|
|
}
|
|
|
|
fn get_rent_exempt<T: Pod>(owner: &Pubkey) -> Account {
|
|
let rent = Rent::default();
|
|
Account::new(rent.minimum_balance(size_of::<T>()), size_of::<T>(), owner)
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_omega_contract() {
|
|
let program_id = Pubkey::new_unique();
|
|
let omega_contract_pk = Pubkey::new_unique();
|
|
let mut omega_contract_acc = get_rent_exempt::<OmegaContract>(&program_id);
|
|
|
|
let oracle_pk = Pubkey::new_unique();
|
|
let mut oracle_acc = Account::default();
|
|
|
|
let quote_mint_pk = Pubkey::new_unique();
|
|
let mut quote_mint_acc = Account::default();
|
|
|
|
let vault_pk = Pubkey::new_unique();
|
|
let mut vault_acc = Account::default();
|
|
|
|
let signer_pk = Pubkey::new_unique(); // doesn't work
|
|
|
|
let instruction = init_omega_contract(
|
|
&program_id, &omega_contract_pk, &oracle_pk, "e_mint_pk, &vault_pk, &signer_pk, &[],
|
|
0, 0, "DO NOT USE THIS CONTRACT"
|
|
).unwrap();
|
|
|
|
let accounts = vec![&mut omega_contract_acc, &mut oracle_acc, &mut quote_mint_acc, &mut vault_acc];
|
|
let result = do_process_instruction(instruction, accounts);
|
|
assert!(result == Ok(()));
|
|
}
|
|
}
|
|
|
|
|