diff --git a/Cargo.lock b/Cargo.lock index 7dac267..f3b1a34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2511,37 +2511,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "ore-delegate-api" -version = "3.7.0" -dependencies = [ - "bytemuck", - "const-crypto", - "mpl-token-metadata", - "num_enum", - "solana-program", - "spl-associated-token-account", - "spl-token 4.0.2", - "steel", - "thiserror 1.0.69", -] - -[[package]] -name = "ore-delegate-program" -version = "3.7.0" -dependencies = [ - "bincode", - "mpl-token-metadata", - "ore-api 3.7.0", - "ore-delegate-api", - "rand 0.8.5", - "solana-program", - "spl-associated-token-account", - "spl-token 4.0.2", - "spl-token-2022 7.0.0", - "steel", -] - [[package]] name = "ore-program" version = "3.7.0" diff --git a/Cargo.toml b/Cargo.toml index 6e099f0..d94b336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["ore/*", "ore-delegate/*", "cli"] +members = ["ore/*", "cli"] [workspace.package] version = "3.7.0" diff --git a/ore/api/src/consts.rs b/ore/api/src/consts.rs index 7450775..c423d56 100644 --- a/ore/api/src/consts.rs +++ b/ore/api/src/consts.rs @@ -20,6 +20,9 @@ pub const MAX_SUPPLY: u64 = ONE_ORE * 5_000_000; /// The seed of the block account PDA. pub const BLOCK: &[u8] = b"block"; +/// The seed of the stake account PDA. +pub const STAKE: &[u8] = b"stake"; + /// The seed of the config account PDA. pub const CONFIG: &[u8] = b"config"; diff --git a/ore/api/src/instruction.rs b/ore/api/src/instruction.rs index 530c221..492f0b5 100644 --- a/ore/api/src/instruction.rs +++ b/ore/api/src/instruction.rs @@ -7,8 +7,10 @@ pub enum OreInstruction { Close = 1, Commit = 2, Decommit = 3, - Mine = 4, - Swap = 5, + Deposit = 4, + Mine = 5, + Swap = 6, + Withdraw = 7, } #[repr(C)] @@ -33,6 +35,12 @@ pub struct Decommit { pub amount: [u8; 8], } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Deposit { + pub amount: [u8; 8], +} + #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Mine { @@ -47,9 +55,17 @@ pub struct Swap { pub precision: u8, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Withdraw { + pub amount: [u8; 8], +} + instruction!(OreInstruction, Open); instruction!(OreInstruction, Close); instruction!(OreInstruction, Commit); instruction!(OreInstruction, Decommit); +instruction!(OreInstruction, Deposit); instruction!(OreInstruction, Mine); instruction!(OreInstruction, Swap); +instruction!(OreInstruction, Withdraw); diff --git a/ore/api/src/state/mod.rs b/ore/api/src/state/mod.rs index 8b6e65f..63c3d25 100644 --- a/ore/api/src/state/mod.rs +++ b/ore/api/src/state/mod.rs @@ -3,6 +3,7 @@ mod config; mod market; mod miner; mod permit; +mod stake; mod treasury; pub use block::*; @@ -10,6 +11,7 @@ pub use config::*; pub use market::*; pub use miner::*; pub use permit::*; +pub use stake::*; pub use treasury::*; use crate::consts::*; @@ -24,7 +26,8 @@ pub enum OreAccount { Market = 102, Miner = 103, Permit = 104, - Treasury = 105, + Stake = 105, + Treasury = 106, } pub fn block_pda(id: u64) -> (Pubkey, u8) { @@ -54,6 +57,13 @@ pub fn permit_pda(authority: Pubkey, block_id: u64) -> (Pubkey, u8) { ) } +pub fn stake_pda(authority: Pubkey, block_id: u64) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[STAKE, &authority.to_bytes(), &block_id.to_le_bytes()], + &crate::ID, + ) +} + pub fn treasury_pda() -> (Pubkey, u8) { Pubkey::find_program_address(&[TREASURY], &crate::ID) } diff --git a/ore/api/src/state/permit.rs b/ore/api/src/state/permit.rs index 52eaa99..2ae6e9f 100644 --- a/ore/api/src/state/permit.rs +++ b/ore/api/src/state/permit.rs @@ -7,13 +7,13 @@ use super::OreAccount; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] pub struct Permit { - /// The amount of ORE this miner has mined. + /// The amount of hash tokens this miner has committed to the block. pub amount: u64, - /// The authority of this miner account. + /// The authority of the miner account. pub authority: Pubkey, - /// The ID of the last block this miner mined in. + /// The ID of the block this permit is for. pub block_id: u64, } diff --git a/ore/api/src/state/stake.rs b/ore/api/src/state/stake.rs new file mode 100644 index 0000000..5d14cb2 --- /dev/null +++ b/ore/api/src/state/stake.rs @@ -0,0 +1,29 @@ +use steel::*; + +use crate::state::stake_pda; + +use super::OreAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Stake { + /// The authority of the miner account. + pub authority: Pubkey, + + /// The ID of the block this collateral is associated with. + pub block_id: u64, + + /// The amount of ORE this miner can commit to the block. + pub capacity: u64, + + /// The amount of ORE this miner has committed to the block. + pub utilization: u64, +} + +impl Stake { + pub fn pda(&self) -> (Pubkey, u8) { + stake_pda(self.authority, self.block_id) + } +} + +account!(OreAccount, Stake); diff --git a/ore/program/src/deposit.rs b/ore/program/src/deposit.rs new file mode 100644 index 0000000..133ee86 --- /dev/null +++ b/ore/program/src/deposit.rs @@ -0,0 +1,72 @@ +use ore_api::prelude::*; +use solana_nostd_keccak::hash; +use solana_program::slot_hashes::SlotHashes; +use steel::*; + +/// Deposits collateral. +pub fn process_deposit(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse data. + let args = Deposit::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts. + let clock = Clock::get()?; + let [signer_info, block_info, collateral_info, market_info, miner_info, mint_ore_info, sender_info, stake_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)?; + collateral_info + .is_writable()? + .as_associated_token_account(block_info.key, mint_ore_info.key)?; + let market = market_info + .as_account::(&ore_api::ID)? + .assert(|m| m.id == block.id)?; + mint_ore_info.has_address(&MINT_ADDRESS)?.as_mint()?; + sender_info + .is_writable()? + .as_associated_token_account(signer_info.key, &mint_ore_info.key)? + .assert(|t| t.amount() >= amount)?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + + // Load stake account. + let stake = if stake_info.data_is_empty() { + create_program_account::( + stake_info, + system_program, + signer_info, + &ore_api::ID, + &[STAKE, &signer_info.key.to_bytes(), &block.id.to_le_bytes()], + )?; + let stake = stake_info.as_account_mut::(&ore_api::ID)?; + stake.authority = *signer_info.key; + stake.block_id = block.id; + stake.capacity = 0; + stake.utilization = 0; + stake + } else { + stake_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *signer_info.key)? + .assert_mut(|p| p.block_id == block.id)? + }; + + // Update stake state. + stake.capacity += amount; + + // Transfer collateral. + transfer( + sender_info, + signer_info, + collateral_info, + token_program, + amount, + )?; + + Ok(()) +} diff --git a/ore/program/src/lib.rs b/ore/program/src/lib.rs index 6b0aa22..0e8a72e 100644 --- a/ore/program/src/lib.rs +++ b/ore/program/src/lib.rs @@ -1,16 +1,20 @@ mod close; mod commit; mod decommit; +mod deposit; mod mine; mod open; mod swap; +mod withdraw; use close::*; use commit::*; use decommit::*; +use deposit::*; use mine::*; use open::*; use swap::*; +use withdraw::*; use ore_api::instruction::*; use steel::*; @@ -27,8 +31,10 @@ pub fn process_instruction( OreInstruction::Close => process_close(accounts, data)?, OreInstruction::Commit => process_commit(accounts, data)?, OreInstruction::Decommit => process_decommit(accounts, data)?, + OreInstruction::Deposit => process_deposit(accounts, data)?, OreInstruction::Mine => process_mine(accounts, data)?, OreInstruction::Swap => process_swap(accounts, data)?, + OreInstruction::Withdraw => process_withdraw(accounts, data)?, } Ok(()) diff --git a/ore/program/src/open.rs b/ore/program/src/open.rs index 3b72de4..8704d4e 100644 --- a/ore/program/src/open.rs +++ b/ore/program/src/open.rs @@ -11,7 +11,7 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Load accounts. let clock = Clock::get()?; - let [signer_info, block_info, commitment_info, market_info, mint_base_info, mint_quote_info, vault_base_info, vault_quote_info, system_program, token_program, associated_token_program, rent_sysvar] = + let [signer_info, block_info, collateral_info, commitment_info, market_info, mint_base_info, mint_quote_info, vault_base_info, vault_quote_info, system_program, token_program, associated_token_program, rent_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -116,7 +116,20 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // TODO Initialize hash token metadata. - // Initialize commitment token account. + // Initialize collateral and commitment token accounts. + if collateral_info.data_is_empty() { + create_associated_token_account( + signer_info, + block_info, + collateral_info, + mint_quote_info, + system_program, + token_program, + associated_token_program, + )?; + } else { + collateral_info.as_associated_token_account(block_info.key, mint_quote_info.key)?; + } if commitment_info.data_is_empty() { create_associated_token_account( signer_info, diff --git a/ore/program/src/swap.rs b/ore/program/src/swap.rs index 88be6f3..4c73059 100644 --- a/ore/program/src/swap.rs +++ b/ore/program/src/swap.rs @@ -11,7 +11,7 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Load accounts. let clock = Clock::get()?; - let [signer_info, block_info, market_info, mint_base_info, mint_quote_info, tokens_base_info, tokens_quote_info, vault_base_info, vault_quote_info, system_program, token_program, associated_token_program] = + let [signer_info, block_info, collateral_info, market_info, mint_base_info, mint_quote_info, stake_info, tokens_base_info, tokens_quote_info, vault_base_info, vault_quote_info, system_program, token_program, associated_token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -20,6 +20,9 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let block: &mut Block = block_info .as_account_mut::(&ore_api::ID)? .assert_mut(|b| clock.slot < b.start_slot)?; + collateral_info + .is_writable()? + .as_associated_token_account(block_info.key, mint_quote_info.key)?; let market = market_info .as_account_mut::(&ore_api::ID)? .assert_mut(|m| m.id == block.id)? @@ -27,6 +30,10 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .assert_mut(|m| m.quote.liquidity() > 0)?; mint_base_info.has_address(&market.base.mint)?.as_mint()?; mint_quote_info.has_address(&market.quote.mint)?.as_mint()?; + stake_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *signer_info.key)? + .assert_mut(|p| p.block_id == block.id)?; vault_base_info .is_writable()? .as_associated_token_account(market_info.key, mint_base_info.key)?; @@ -37,6 +44,28 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult token_program.is_program(&spl_token::ID)?; associated_token_program.is_program(&spl_associated_token_account::ID)?; + // Load stake account. + let stake = if stake_info.data_is_empty() { + create_program_account::( + stake_info, + system_program, + signer_info, + &ore_api::ID, + &[STAKE, &signer_info.key.to_bytes(), &block.id.to_le_bytes()], + )?; + let stake = stake_info.as_account_mut::(&ore_api::ID)?; + stake.authority = *signer_info.key; + stake.block_id = block.id; + stake.capacity = 0; + stake.utilization = 0; + stake + } else { + stake_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *signer_info.key)? + .assert_mut(|p| p.block_id == block.id)? + }; + // Load token acccounts. if tokens_base_info.data_is_empty() { create_associated_token_account( @@ -94,6 +123,21 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult ), }; + // Update stake state. + match direction { + SwapDirection::Buy => { + stake.utilization += in_amount; + } + SwapDirection::Sell => { + stake.utilization = stake.utilization.saturating_sub(out_amount); + } + } + + // Assert utilization is not greater than capacity. + if stake.utilization > stake.capacity { + panic!("utilization is greater than capacity"); + } + // Transfer tokens. transfer(signer_info, in_from, in_to, token_program, in_amount)?; transfer_signed( diff --git a/ore/program/src/withdraw.rs b/ore/program/src/withdraw.rs new file mode 100644 index 0000000..2749cb6 --- /dev/null +++ b/ore/program/src/withdraw.rs @@ -0,0 +1,59 @@ +use ore_api::prelude::*; +use solana_nostd_keccak::hash; +use solana_program::slot_hashes::SlotHashes; +use steel::*; + +/// Withdraws collateral. +pub fn process_withdraw(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse data. + let args = Withdraw::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts. + let clock = Clock::get()?; + let [signer_info, block_info, collateral_info, mint_ore_info, recipient_info, stake_info, system_program, token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + collateral_info + .is_writable()? + .as_associated_token_account(block_info.key, mint_ore_info.key)?; + mint_ore_info.has_address(&MINT_ADDRESS)?.as_mint()?; + recipient_info + .is_writable()? + .as_associated_token_account(signer_info.key, &mint_ore_info.key)?; + let stake = stake_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *signer_info.key)?; + block_info.has_seeds(&[BLOCK, &stake.block_id.to_le_bytes()], &ore_api::ID)?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + + // Check timestamp. Collateral can only be withdrawn after the block has started mining + let start_slot = stake.block_id * 1500; + if clock.slot < start_slot { + return Err(ProgramError::InvalidArgument); + } + + // Update stake state. + stake.capacity -= amount; + + // Transfer collateral. + transfer_signed( + block_info, + collateral_info, + recipient_info, + token_program, + amount, + &[BLOCK, &stake.block_id.to_le_bytes()], + )?; + + // Close stake account, if empty. + if stake.capacity == 0 { + stake_info.close(signer_info)?; + } + + Ok(()) +}