diff --git a/api/src/consts.rs b/api/src/consts.rs index 2958e37..224e1da 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -116,3 +116,6 @@ pub const DENOMINATOR_BPS: u64 = 10_000; /// Slot window size, used for sandwich resistance. pub const SLOT_WINDOW: u64 = 4; + +/// Amount of hash tokens to mint to market. +pub const HASH_TOKEN_SUPPLY: u64 = 10_000_000; diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 7882b8a..1e3528d 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -11,11 +11,10 @@ pub enum OreInstruction { // Stake Deposit = 3, Withdraw = 4, - Free = 5, // Trade - Buy = 6, - Sell = 7, + Free = 5, + Swap = 6, } #[repr(C)] @@ -52,11 +51,11 @@ pub struct Free {} #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Buy {} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Sell {} +pub struct Swap { + pub amount: [u8; 8], + pub direction: u8, + pub precision: u8, +} instruction!(OreInstruction, Open); instruction!(OreInstruction, Close); @@ -64,5 +63,4 @@ instruction!(OreInstruction, Mine); instruction!(OreInstruction, Deposit); instruction!(OreInstruction, Withdraw); instruction!(OreInstruction, Free); -instruction!(OreInstruction, Buy); -instruction!(OreInstruction, Sell); +instruction!(OreInstruction, Swap); diff --git a/program/src/buy.rs b/program/src/buy.rs deleted file mode 100644 index c41410a..0000000 --- a/program/src/buy.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Buy hashpower. -pub fn process_buy(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { - // Load accounts. - let clock = Clock::get()?; - let [signer_info, block_info, market_info, miner_info, recipient_info, system_program, token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let block = block_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| clock.slot >= b.start_slot + 1500)?; - system_program.is_program(&system_program::ID)?; - token_program.is_program(&spl_token::ID)?; - - // TODO Buy hash tokens - - Ok(()) -} diff --git a/program/src/lib.rs b/program/src/lib.rs index fe2d02c..b1ebbb9 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,19 +1,17 @@ -mod buy; mod close; mod deposit; mod free; mod mine; mod open; -mod sell; +mod swap; mod withdraw; -use buy::*; use close::*; use deposit::*; use free::*; use mine::*; use open::*; -use sell::*; +use swap::*; use withdraw::*; use ore_api::instruction::*; @@ -35,11 +33,10 @@ pub fn process_instruction( // Stake OreInstruction::Deposit => process_deposit(accounts, data)?, OreInstruction::Withdraw => process_withdraw(accounts, data)?, - OreInstruction::Free => process_free(accounts, data)?, // Trade - OreInstruction::Buy => process_buy(accounts, data)?, - OreInstruction::Sell => process_sell(accounts, data)?, + OreInstruction::Free => process_free(accounts, data)?, + OreInstruction::Swap => process_swap(accounts, data)?, } Ok(()) diff --git a/program/src/open.rs b/program/src/open.rs index e6e80e2..876e676 100644 --- a/program/src/open.rs +++ b/program/src/open.rs @@ -1,5 +1,6 @@ use ore_api::prelude::*; use solana_program::program_pack::Pack; +use spl_token_2022::instruction::AuthorityType; use steel::*; /// Opens a new block. @@ -9,7 +10,7 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let id = u64::from_le_bytes(args.id); // Load accounts. - let [signer_info, block_info, market_info, market_hash_info, market_ore_info, mint_hash_info, mint_ore_info, system_program, token_program, associated_token_program, rent_sysvar] = + let [signer_info, block_info, market_info, mint_base_info, mint_quote_info, system_program, vault_base_info, vault_quote_info, token_program, associated_token_program, rent_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -23,11 +24,11 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .is_empty()? .is_writable()? .has_seeds(&[MARKET, &id.to_le_bytes()], &ore_api::ID)?; - mint_hash_info + mint_base_info .is_empty()? .is_writable()? .has_seeds(&[MINT, &id.to_le_bytes()], &ore_api::ID)?; - mint_ore_info.has_address(&MINT_ADDRESS)?.as_mint()?; + mint_quote_info.has_address(&MINT_ADDRESS)?.as_mint()?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; associated_token_program.is_program(&spl_associated_token_account::ID)?; @@ -59,14 +60,14 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult )?; let market = market_info.as_account_mut::(&ore_api::ID)?; market.base = TokenParams { - mint: Pubkey::default(), - balance: 0, + mint: *mint_base_info.key, + balance: HASH_TOKEN_SUPPLY, balance_virtual: 0, }; market.quote = TokenParams { - mint: Pubkey::default(), + mint: *mint_quote_info.key, balance: 0, - balance_virtual: 0, + balance_virtual: ONE_ORE, }; market.fee = FeeParams { rate: FEE_RATE_BPS, @@ -83,7 +84,7 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Initialize hash token mint. allocate_account( - mint_hash_info, + mint_base_info, system_program, signer_info, spl_token::state::Mint::LEN, @@ -91,7 +92,7 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult &[MINT, &id.to_le_bytes()], )?; initialize_mint_signed( - mint_hash_info, + mint_base_info, block_info, None, token_program, @@ -102,50 +103,50 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // TODO Initialize hash token metadata. - // Initialize token accounts for market. - if market_hash_info.data_is_empty() { + // Initialize vault token accounts. + if vault_base_info.data_is_empty() { create_associated_token_account( signer_info, market_info, - market_hash_info, - mint_hash_info, + vault_base_info, + mint_base_info, system_program, token_program, associated_token_program, )?; } else { - market_hash_info.as_associated_token_account(market_info.key, mint_hash_info.key)?; + vault_base_info.as_associated_token_account(market_info.key, mint_base_info.key)?; } - if market_ore_info.data_is_empty() { + if vault_quote_info.data_is_empty() { create_associated_token_account( signer_info, market_info, - market_ore_info, - mint_ore_info, + vault_quote_info, + mint_quote_info, system_program, token_program, associated_token_program, )?; } else { - market_ore_info.as_associated_token_account(market_info.key, mint_ore_info.key)?; + vault_quote_info.as_associated_token_account(market_info.key, mint_quote_info.key)?; } // Mint hash tokens to market. mint_to_signed( - mint_hash_info, - market_hash_info, + mint_base_info, + vault_base_info, block_info, token_program, - 10_000_000, + HASH_TOKEN_SUPPLY, &[BLOCK, &id.to_le_bytes()], )?; // Burn mint authority. set_authority_signed( - mint_hash_info, + mint_base_info, block_info, None, - spl_token_2022::instruction::AuthorityType::MintTokens, + AuthorityType::MintTokens, token_program, &[BLOCK, &id.to_le_bytes()], )?; diff --git a/program/src/sell.rs b/program/src/sell.rs deleted file mode 100644 index ca10b9a..0000000 --- a/program/src/sell.rs +++ /dev/null @@ -1,23 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Sell hashpower. -pub fn process_sell(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { - // Load accounts. - let clock = Clock::get()?; - let [signer_info, block_info, market_info, miner_info, recipient_info, system_program, token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let block = block_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|b| clock.slot >= b.start_slot + 1500)?; - system_program.is_program(&system_program::ID)?; - token_program.is_program(&spl_token::ID)?; - - // TODO Buy hash tokens - - Ok(()) -} diff --git a/program/src/swap.rs b/program/src/swap.rs new file mode 100644 index 0000000..6a59d3b --- /dev/null +++ b/program/src/swap.rs @@ -0,0 +1,115 @@ +use ore_api::prelude::*; +use steel::*; + +/// Swap in a hashpower market. +pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse args. + let args = Swap::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + let direction = SwapDirection::try_from(args.direction).unwrap(); + let precision = SwapPrecision::try_from(args.precision).unwrap(); + + // Load accounts. + let clock = Clock::get()?; + let [signer_info, block_info, market_info, miner_info, mint_base_info, mint_quote_info, receipt_info, tokens_base_info, tokens_quote_info, vault_base_info, vault_quote_info, system_program, token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let block = block_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| clock.slot >= b.start_slot + 1500)?; + let market = market_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.id == block.id)? + .assert_mut_err( + |m| m.base.reserves() > 0, + OreError::InsufficientLiquidity.into(), + )? + .assert_mut_err( + |m| m.quote.reserves() > 0, + OreError::InsufficientLiquidity.into(), + )?; + let miner = miner_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.authority == *signer_info.key)?; + mint_base_info.has_address(&market.base.mint)?.as_mint()?; + mint_quote_info.has_address(&market.quote.mint)?.as_mint()?; + tokens_base_info + .is_writable()? + .as_associated_token_account(signer_info.key, mint_base_info.key)?; + tokens_quote_info + .is_writable()? + .as_associated_token_account(signer_info.key, mint_quote_info.key)?; + vault_base_info + .is_writable()? + .as_associated_token_account(market_info.key, mint_base_info.key)?; + vault_quote_info + .is_writable()? + .as_associated_token_account(market_info.key, mint_quote_info.key)?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + + // TODO Buy hash tokens + + // Update market state. + let swap_result = market.swap(amount, direction, precision, clock)?; + swap_result.log_return(); + + // Get transfer amounts and accounts. + let (in_amount, in_from, in_to, out_amount, out_from, out_to) = match direction { + SwapDirection::Buy => ( + swap_result.quote_to_transfer, + tokens_quote_info, + vault_quote_info, + swap_result.base_to_transfer, + vault_base_info, + tokens_base_info, + ), + SwapDirection::Sell => ( + swap_result.base_to_transfer, + tokens_base_info, + vault_base_info, + swap_result.quote_to_transfer, + vault_quote_info, + tokens_quote_info, + ), + }; + + // Update miner state. + match direction { + SwapDirection::Buy => { + miner.deployed += in_amount; + assert!(miner.deployed <= miner.stake); + } + SwapDirection::Sell => { + miner.deployed = miner.deployed.saturating_sub(out_amount); + } + } + + // Transfer tokens. + transfer(signer_info, in_from, in_to, token_program, in_amount)?; + transfer_signed( + market_info, + out_from, + out_to, + token_program, + out_amount, + &[ + MARKET, + market.base.mint.as_ref(), + market.quote.mint.as_ref(), + market.id.to_le_bytes().as_ref(), + ], + )?; + + // Validate vault reserves. + let vault_base = + vault_base_info.as_associated_token_account(market_info.key, mint_base_info.key)?; + let vault_quote = + vault_quote_info.as_associated_token_account(market_info.key, mint_quote_info.key)?; + market.check_vaults(&vault_base, &vault_quote)?; + + Ok(()) +}