diff --git a/README.md b/README.md index ea50a53..2bf4cac 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,8 @@ ## Programs - [`ore`](ore/) – ORE mining program. - [`ore-delegate`](ore-delegate/) – Delegate mining to another party. + + +TODO +- [ ] Hash token metadata +- [ ] \ No newline at end of file diff --git a/ore/api/src/consts.rs b/ore/api/src/consts.rs index ad72f13..7450775 100644 --- a/ore/api/src/consts.rs +++ b/ore/api/src/consts.rs @@ -32,6 +32,9 @@ pub const MINER: &[u8] = b"miner"; /// The seed of the mint account PDA. pub const MINT: &[u8] = b"mint"; +/// The seed of the permit account PDA. +pub const PERMIT: &[u8] = b"permit"; + /// The seed of the treasury account PDA. pub const TREASURY: &[u8] = b"treasury"; diff --git a/ore/api/src/instruction.rs b/ore/api/src/instruction.rs index 000718e..530c221 100644 --- a/ore/api/src/instruction.rs +++ b/ore/api/src/instruction.rs @@ -5,8 +5,10 @@ use steel::*; pub enum OreInstruction { Open = 0, Close = 1, - Mine = 2, - Swap = 3, + Commit = 2, + Decommit = 3, + Mine = 4, + Swap = 5, } #[repr(C)] @@ -19,6 +21,18 @@ pub struct Open { #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Close {} +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Commit { + pub amount: [u8; 8], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Decommit { + pub amount: [u8; 8], +} + #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Mine { @@ -35,5 +49,7 @@ pub struct Swap { instruction!(OreInstruction, Open); instruction!(OreInstruction, Close); +instruction!(OreInstruction, Commit); +instruction!(OreInstruction, Decommit); instruction!(OreInstruction, Mine); instruction!(OreInstruction, Swap); diff --git a/ore/api/src/state/mod.rs b/ore/api/src/state/mod.rs index afb75b1..8b6e65f 100644 --- a/ore/api/src/state/mod.rs +++ b/ore/api/src/state/mod.rs @@ -2,12 +2,14 @@ mod block; mod config; mod market; mod miner; +mod permit; mod treasury; pub use block::*; pub use config::*; pub use market::*; pub use miner::*; +pub use permit::*; pub use treasury::*; use crate::consts::*; @@ -21,7 +23,8 @@ pub enum OreAccount { Config = 101, Market = 102, Miner = 103, - Treasury = 104, + Permit = 104, + Treasury = 105, } pub fn block_pda(id: u64) -> (Pubkey, u8) { @@ -44,6 +47,13 @@ pub fn mint_pda(id: u64) -> (Pubkey, u8) { Pubkey::find_program_address(&[MINT, &id.to_le_bytes()], &crate::ID) } +pub fn permit_pda(authority: Pubkey, block_id: u64) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[PERMIT, &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 new file mode 100644 index 0000000..52eaa99 --- /dev/null +++ b/ore/api/src/state/permit.rs @@ -0,0 +1,26 @@ +use steel::*; + +use crate::state::permit_pda; + +use super::OreAccount; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Permit { + /// The amount of ORE this miner has mined. + pub amount: u64, + + /// The authority of this miner account. + pub authority: Pubkey, + + /// The ID of the last block this miner mined in. + pub block_id: u64, +} + +impl Permit { + pub fn pda(&self) -> (Pubkey, u8) { + permit_pda(self.authority, self.block_id) + } +} + +account!(OreAccount, Permit); diff --git a/ore/program/src/commit.rs b/ore/program/src/commit.rs new file mode 100644 index 0000000..ca2d69a --- /dev/null +++ b/ore/program/src/commit.rs @@ -0,0 +1,73 @@ +use ore_api::prelude::*; +use steel::*; + +/// Commit to a block. +pub fn process_commit(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse data. + let args = Commit::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts. + let clock = Clock::get()?; + let [signer_info, block_info, commitment_info, market_info, miner_info, mint_info, permit_info, sender_info, system_program, token_program, slot_hashes_sysvar] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let block = block_info + .as_account::(&ore_api::ID)? + .assert(|b| clock.slot < b.start_slot)?; + commitment_info.as_associated_token_account(block_info.key, mint_info.key)?; + let market = market_info + .as_account::(&ore_api::ID)? + .assert(|m| m.id == block.id)?; + miner_info + .as_account::(&ore_api::ID)? + .assert(|m| m.authority == *signer_info.key)?; + mint_info.has_address(&market.base.mint)?.as_mint()?; + let sender = sender_info + .is_writable()? + .as_associated_token_account(signer_info.key, &mint_info.key)?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; + + // Normalize amount. + let amount = sender.amount().min(amount); + + // Load permit account. + let permit = if permit_info.data_is_empty() { + create_program_account::( + permit_info, + system_program, + signer_info, + &ore_api::ID, + &[PERMIT, &signer_info.key.to_bytes(), &block.id.to_le_bytes()], + )?; + let permit = permit_info.as_account_mut::(&ore_api::ID)?; + permit.authority = *signer_info.key; + permit.block_id = block.id; + permit.amount = 0; + permit + } else { + permit_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *signer_info.key)? + .assert_mut(|p| p.block_id == block.id)? + }; + + // Transfer hash tokens. + transfer( + signer_info, + sender_info, + commitment_info, + token_program, + amount, + )?; + + // Update block. + permit.amount += amount; + + Ok(()) +} diff --git a/ore/program/src/decommit.rs b/ore/program/src/decommit.rs new file mode 100644 index 0000000..652573a --- /dev/null +++ b/ore/program/src/decommit.rs @@ -0,0 +1,64 @@ +use ore_api::prelude::*; +use steel::*; + +/// Decommit from a block. +pub fn process_decommit(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse data. + let args = Decommit::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts. + let clock = Clock::get()?; + let [signer_info, block_info, commitment_info, market_info, miner_info, mint_info, permit_info, recipient_info, system_program, token_program, slot_hashes_sysvar] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let block = block_info + .as_account::(&ore_api::ID)? + .assert(|b| clock.slot < b.start_slot)?; + commitment_info + .is_writable()? + .as_associated_token_account(block_info.key, mint_info.key)?; + let market = market_info + .as_account::(&ore_api::ID)? + .assert(|m| m.id == block.id)?; + miner_info + .as_account::(&ore_api::ID)? + .assert(|m| m.authority == *signer_info.key)?; + mint_info.has_address(&market.base.mint)?.as_mint()?; + let permit = permit_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *signer_info.key)? + .assert_mut(|p| p.block_id == block.id)?; + recipient_info + .is_writable()? + .as_associated_token_account(signer_info.key, &mint_info.key)?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; + + // Normalize amount. + let amount = permit.amount.min(amount); + + // Transfer hash tokens. + transfer_signed( + block_info, + commitment_info, + recipient_info, + token_program, + amount, + &[BLOCK, &block.id.to_le_bytes()], + )?; + + // Update block. + permit.amount -= amount; + + // Close permit, if empty. + if permit.amount == 0 { + permit_info.close(signer_info)?; + } + + Ok(()) +} diff --git a/ore/program/src/lib.rs b/ore/program/src/lib.rs index bf709ea..2ed2b5a 100644 --- a/ore/program/src/lib.rs +++ b/ore/program/src/lib.rs @@ -1,9 +1,15 @@ mod close; +mod commit; +mod decommit; mod mine; mod open; mod swap; +use core::panic; + use close::*; +use commit::*; +use decommit::*; use mine::*; use open::*; use swap::*; @@ -21,6 +27,8 @@ pub fn process_instruction( match ix { OreInstruction::Open => process_open(accounts, data)?, OreInstruction::Close => process_close(accounts, data)?, + OreInstruction::Commit => process_commit(accounts, data)?, + OreInstruction::Decommit => process_decommit(accounts, data)?, OreInstruction::Mine => process_mine(accounts, data)?, OreInstruction::Swap => process_swap(accounts, data)?, } diff --git a/ore/program/src/mine.rs b/ore/program/src/mine.rs index 2860167..3f02cc6 100644 --- a/ore/program/src/mine.rs +++ b/ore/program/src/mine.rs @@ -11,7 +11,7 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Load accounts. let clock = Clock::get()?; - let [signer_info, block_info, market_info, miner_info, mint_hash_info, mint_ore_info, recipient_info, sender_info, treasury_info, system_program, token_program, slot_hashes_sysvar] = + let [signer_info, block_info, market_info, miner_info, mint_hash_info, mint_ore_info, permit_info, recipient_info, sender_info, treasury_info, system_program, token_program, slot_hashes_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -26,6 +26,9 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .assert(|m| m.id == block.id)?; mint_hash_info.has_address(&market.base.mint)?.as_mint()?; mint_ore_info.has_address(&MINT_ADDRESS)?.as_mint()?; + permit_info + .as_account::(&ore_api::ID)? + .assert(|p| p.authority == *signer_info.key)?; recipient_info .is_writable()? .as_associated_token_account(signer_info.key, &MINT_ADDRESS)?; diff --git a/ore/program/src/open.rs b/ore/program/src/open.rs index 3f7bcde..3b72de4 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, 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, 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,6 +116,21 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // TODO Initialize hash token metadata. + // Initialize commitment token account. + if commitment_info.data_is_empty() { + create_associated_token_account( + signer_info, + block_info, + commitment_info, + mint_base_info, + system_program, + token_program, + associated_token_program, + )?; + } else { + commitment_info.as_associated_token_account(block_info.key, mint_base_info.key)?; + } + // Initialize vault token accounts. if vault_base_info.data_is_empty() { create_associated_token_account(