scaffolding

This commit is contained in:
Hardhat Chad
2025-06-05 16:14:08 -07:00
parent fa1fb5e30c
commit 4138fc2b66
17 changed files with 393 additions and 64 deletions

1
Cargo.lock generated
View File

@@ -1325,6 +1325,7 @@ dependencies = [
"solana-program", "solana-program",
"spl-associated-token-account", "spl-associated-token-account",
"spl-token 4.0.2", "spl-token 4.0.2",
"spl-token-2022 7.0.0",
"steel", "steel",
] ]

View File

@@ -29,6 +29,7 @@ solana-program = "^2.1"
solana-client = "^2.1" solana-client = "^2.1"
solana-sdk = "^2.1" solana-sdk = "^2.1"
spl-token = { version = "^4", features = ["no-entrypoint"] } spl-token = { version = "^4", features = ["no-entrypoint"] }
spl-token-2022 = "^7"
spl-associated-token-account = { version = "^6", features = [ "no-entrypoint" ] } spl-associated-token-account = { version = "^6", features = [ "no-entrypoint" ] }
steel = { features = ["spl"], version = "4.0" } steel = { features = ["spl"], version = "4.0" }
thiserror = "1.0.57" thiserror = "1.0.57"

View File

@@ -1,6 +1,6 @@
# ORE # ORE
**Digital gold, accelerated.** **Mine blockspace. Trade hashpower. Win rewards.**
## API ## API
- [`Consts`](api/src/consts.rs)  Program constants. - [`Consts`](api/src/consts.rs)  Program constants.
@@ -9,18 +9,28 @@
- [`Instruction`](api/src/instruction.rs)  Declared instructions and arguments. - [`Instruction`](api/src/instruction.rs)  Declared instructions and arguments.
## Instructions ## Instructions
- [`Bury`](program/src/bury.rs) - Swap committed tokens into ORE and burns it.
- [`Close`](program/src/close.rs) - Close a commit account. #### Mine
- [`Deploy`](program/src/deploy.rs) - Deploy capital to mine the current block. - [`Open`](program/src/open.rs) - Opens a new block for mining.
- [`Initialize`](program/src/initialize.rs) - Initialize the program. - [`Close`](program/src/close.rs) - Closes a block and pays out rewards.
- [`Payout`](program/src/payout.rs) - Payout the block reward to the winning commit. - [`Mine`](program/src/mine.rs) - Mines the current block by computing hashes.
- [`Reset`](program/src/reset.rs) - Start the next block.
#### Stake
- [`Deposit`](program/src/deposit.rs) - Deposits stake into a miner account.
- [`Withdraw`](program/src/withdraw.rs) - Withdraws stake from a miner account.
- [`Free`](program/src/free.rs) - Frees up miner capacity after block ends.
#### Trade
- [`Buy`](program/src/buy.rs) - Buys hash tokens from the market.
- [`Sell`](program/src/sell.rs) - Sells hash tokens to the market.
## State ## State
- [`Block`](api/src/state/block.rs) - A singleton account tracking rounds of commits. - [`Block`](api/src/state/block.rs) - A period of time for mining.
- [`Proof`](api/src/state/proof.rs) - (Deprecated) An account which tracks a miner's current hash and current stake. - [`Config`](api/src/state/config.rs) - Global program configuration.
- [`Treasury`](api/src/state/treasury.rs) The mint authority on the ORE token. - [`Market`](api/src/state/market.rs) - Hashpower market for a given block.
- [`Commit`](api/src/state/commit.rs) - Capital deployed by a miner in the current block. - [`Miner`](api/src/state/miner.rs) - A user's mining and staking state.
- [`Receipt`](api/src/state/receipt.rs) - Tracks a miner's deployed capital.
- [`Treasury`](api/src/state/treasury.rs) - The mint authority on the ORE token.
## Tests ## Tests

View File

@@ -3,19 +3,19 @@ use steel::*;
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] #[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
pub enum OreInstruction { pub enum OreInstruction {
// Block // Mine
Open = 0, Open = 0,
Close = 1, Close = 1,
Mine = 2, Mine = 2,
// Market
Buy = 3,
Sell = 4,
// Stake // Stake
Deposit = 5, Deposit = 3,
Withdraw = 6, Withdraw = 4,
Free = 7, Free = 5,
// Trade
Buy = 6,
Sell = 7,
} }
#[repr(C)] #[repr(C)]
@@ -34,6 +34,22 @@ pub struct Mine {
pub amount: [u8; 8], 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 Withdraw {
pub amount: [u8; 8],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Free {}
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)] #[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Buy {} pub struct Buy {}
@@ -42,23 +58,11 @@ pub struct Buy {}
#[derive(Clone, Copy, Debug, Pod, Zeroable)] #[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Sell {} pub struct Sell {}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Deposit {}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Withdraw {}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct Free {}
instruction!(OreInstruction, Open); instruction!(OreInstruction, Open);
instruction!(OreInstruction, Close); instruction!(OreInstruction, Close);
instruction!(OreInstruction, Mine); instruction!(OreInstruction, Mine);
instruction!(OreInstruction, Buy);
instruction!(OreInstruction, Sell);
instruction!(OreInstruction, Deposit); instruction!(OreInstruction, Deposit);
instruction!(OreInstruction, Withdraw); instruction!(OreInstruction, Withdraw);
instruction!(OreInstruction, Free); instruction!(OreInstruction, Free);
instruction!(OreInstruction, Buy);
instruction!(OreInstruction, Sell);

View File

@@ -6,7 +6,10 @@ use super::OreAccount;
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Market { pub struct Market {
/// The id of the block this market is associated with. /// The id of the block this market is associated with.
pub block_id: u64, pub id: u64,
/// Mint of the hash token.
pub mint: Pubkey,
} }
// TODO Bonding curve stuff // TODO Bonding curve stuff

View File

@@ -11,8 +11,8 @@ pub struct Miner {
/// The ID of the last block this miner mined in. /// The ID of the last block this miner mined in.
pub block_id: u64, pub block_id: u64,
/// The amount of ORE this miner can deploy into hashpower markets. /// The amount of ORE this miner has deployed into hashpower markets.
pub capacity: u64, pub deployed: u64,
/// The hash of the last block this miner mined in. /// The hash of the last block this miner mined in.
pub hash: [u8; 32], pub hash: [u8; 32],

View File

@@ -6,6 +6,9 @@ use super::OreAccount;
/// the program's global token account. /// the program's global token account.
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Treasury {} pub struct Treasury {
/// The total amount of ORE that has been staked.
pub total_stake: u64,
}
account!(OreAccount, Treasury); account!(OreAccount, Treasury);

View File

@@ -25,6 +25,7 @@ ore-api.workspace = true
ore-boost-api.workspace = true ore-boost-api.workspace = true
solana-program.workspace = true solana-program.workspace = true
spl-token.workspace = true spl-token.workspace = true
spl-token-2022.workspace = true
spl-associated-token-account.workspace = true spl-associated-token-account.workspace = true
steel.workspace = true steel.workspace = true

View File

@@ -0,0 +1,23 @@
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::<Block>(&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(())
}

View File

@@ -0,0 +1,69 @@
use ore_api::prelude::*;
use steel::*;
/// Closes a block.
pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, block_info, market_info, market_hash_info, market_ore_info, mint_hash_info, mint_ore_info, recipient_info, treasury_info, system_program, token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let block = block_info
.as_account_mut::<Block>(&ore_api::ID)?
.assert_mut(|b| clock.slot >= b.start_slot + 1500)?;
let market = market_info
.as_account_mut::<Market>(&ore_api::ID)?
.assert_mut(|m| m.id == block.id)?;
let market_hash =
market_hash_info.as_associated_token_account(market_info.key, mint_hash_info.key)?;
let market_ore =
market_ore_info.as_associated_token_account(market_info.key, mint_ore_info.key)?;
mint_hash_info.has_address(&market.mint)?;
mint_ore_info.has_address(&MINT_ADDRESS)?;
system_program.is_program(&system_program::ID)?;
token_program.is_program(&spl_token::ID)?;
// Payout block reward.
if block.best_miner != Pubkey::default() {
recipient_info.as_associated_token_account(&block.best_miner, &MINT_ADDRESS)?;
mint_to_signed(
mint_ore_info,
recipient_info,
treasury_info,
token_program,
block.reward,
&[TREASURY],
)?;
}
// Burn hash tokens.
burn_signed(
market_hash_info,
mint_hash_info,
market_info,
token_program,
market_hash.amount(),
&[MARKET, &market.id.to_le_bytes()],
)?;
// Burn ORE liquidity.
burn_signed(
market_ore_info,
mint_ore_info,
market_info,
token_program,
market_ore.amount(),
&[MARKET, &market.id.to_le_bytes()],
)?;
// Close block.
block_info.close(signer_info)?;
// Close market.
market_info.close(signer_info)?;
Ok(())
}

View File

@@ -0,0 +1,39 @@
use ore_api::prelude::*;
use steel::*;
/// Deposits stake.
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 [signer_info, miner_info, sender_info, treasury_info, treasury_tokens_info, token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let miner = miner_info
.as_account_mut::<Miner>(&ore_api::ID)?
.assert_mut(|m| m.authority == *signer_info.key)?;
sender_info.as_associated_token_account(&signer_info.key, &MINT_ADDRESS)?;
let treasury = treasury_info.as_account_mut::<Treasury>(&ore_api::ID)?;
treasury_tokens_info.as_associated_token_account(&treasury_info.key, &MINT_ADDRESS)?;
token_program.is_program(&spl_token::ID)?;
// Update account state.
miner.stake += amount;
treasury.total_stake += amount;
// Execute transfer.
transfer(
signer_info,
sender_info,
treasury_tokens_info,
token_program,
amount,
)?;
Ok(())
}

View File

@@ -0,0 +1,31 @@
use ore_api::prelude::*;
use steel::*;
/// Free up capacity.
pub fn process_free(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, miner_info, receipt_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let miner = miner_info
.as_account_mut::<Miner>(&ore_api::ID)?
.assert_mut(|m| m.authority == *signer_info.key)?;
let receipt = receipt_info
.as_account_mut::<Receipt>(&ore_api::ID)?
.assert_mut(|r| r.authority == *signer_info.key)?;
// Asset that block has ended.
let start_slot = 1500 * receipt.block_id;
let end_slot = start_slot + 1500;
assert!(clock.slot >= end_slot, "Block has not yet closed.");
// Free up miner capacity.
miner.deployed -= receipt.amount;
// Close the receipt.
receipt_info.close(signer_info)?;
Ok(())
}

View File

@@ -27,10 +27,19 @@ pub fn process_instruction(
let (ix, data) = parse_instruction(&ore_api::ID, program_id, data)?; let (ix, data) = parse_instruction(&ore_api::ID, program_id, data)?;
match ix { match ix {
// Mine
OreInstruction::Open => process_open(accounts, data)?, OreInstruction::Open => process_open(accounts, data)?,
OreInstruction::Close => process_close(accounts, data)?,
OreInstruction::Mine => process_mine(accounts, data)?, OreInstruction::Mine => process_mine(accounts, data)?,
_ => panic!("Not implemented"), // 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)?,
} }
Ok(()) Ok(())

View File

@@ -2,7 +2,7 @@ use ore_api::prelude::*;
use solana_program::keccak; use solana_program::keccak;
use steel::*; use steel::*;
/// Opens a new block for hashpower trading. /// Mine a block.
pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse data. // Parse data.
let args = Mine::try_from_bytes(data)?; let args = Mine::try_from_bytes(data)?;

View File

@@ -1,14 +1,15 @@
use ore_api::prelude::*; use ore_api::prelude::*;
use solana_program::program_pack::Pack;
use steel::*; use steel::*;
/// Opens a new block for hashpower trading. /// Opens a new block.
pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse data. // Parse data.
let args = Open::try_from_bytes(data)?; let args = Open::try_from_bytes(data)?;
let id = u64::from_le_bytes(args.id); let id = u64::from_le_bytes(args.id);
// Load accounts. // Load accounts.
let [signer_info, block_info, system_program, token_program, associated_token_program] = 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] =
accounts accounts
else { else {
return Err(ProgramError::NotEnoughAccountKeys); return Err(ProgramError::NotEnoughAccountKeys);
@@ -18,9 +19,19 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult
.is_empty()? .is_empty()?
.is_writable()? .is_writable()?
.has_seeds(&[BLOCK, &id.to_le_bytes()], &ore_api::ID)?; .has_seeds(&[BLOCK, &id.to_le_bytes()], &ore_api::ID)?;
market_info
.is_empty()?
.is_writable()?
.has_seeds(&[MARKET, &id.to_le_bytes()], &ore_api::ID)?;
mint_hash_info
.is_empty()?
.is_writable()?
.has_seeds(&[MINT, &id.to_le_bytes()], &ore_api::ID)?;
mint_ore_info.has_address(&MINT_ADDRESS)?.as_mint()?;
system_program.is_program(&system_program::ID)?; system_program.is_program(&system_program::ID)?;
token_program.is_program(&spl_token::ID)?; token_program.is_program(&spl_token::ID)?;
associated_token_program.is_program(&spl_associated_token_account::ID)?; associated_token_program.is_program(&spl_associated_token_account::ID)?;
rent_sysvar.is_sysvar(&sysvar::rent::ID)?;
// Initialize config. // Initialize config.
create_program_account::<Block>( create_program_account::<Block>(
@@ -38,30 +49,85 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult
block.slot_hash = [0; 32]; block.slot_hash = [0; 32];
block.start_slot = 1500 * id; block.start_slot = 1500 * id;
// TODO Init market // Initialize market.
// TODO Init hash token mint create_program_account::<Market>(
// TODO Init token accounts for market market_info,
// TODO Init mint hash tokens to market system_program,
signer_info,
&ore_api::ID,
&[MARKET, &id.to_le_bytes()],
)?;
let market = market_info.as_account_mut::<Market>(&ore_api::ID)?;
market.id = id;
// // Initialize block token accounts. // Initialize hash token mint.
// create_associated_token_account( allocate_account(
// signer_info, mint_hash_info,
// block_info, system_program,
// block_commits_info, signer_info,
// sol_mint_info, spl_token::state::Mint::LEN,
// system_program, &spl_token::ID,
// token_program, &[MINT, &id.to_le_bytes()],
// associated_token_program, )?;
// )?; initialize_mint_signed(
// create_associated_token_account( mint_hash_info,
// signer_info, block_info,
// block_info, None,
// block_ore_info, token_program,
// ore_mint_info, rent_sysvar,
// system_program, 0,
// token_program, &[MINT, &id.to_le_bytes()],
// associated_token_program, )?;
// )?;
// TODO Initialize hash token metadata.
// Initialize token accounts for market.
if market_hash_info.data_is_empty() {
create_associated_token_account(
signer_info,
market_info,
market_hash_info,
mint_hash_info,
system_program,
token_program,
associated_token_program,
)?;
} else {
market_hash_info.as_associated_token_account(market_info.key, mint_hash_info.key)?;
}
if market_ore_info.data_is_empty() {
create_associated_token_account(
signer_info,
market_info,
market_ore_info,
mint_ore_info,
system_program,
token_program,
associated_token_program,
)?;
} else {
market_ore_info.as_associated_token_account(market_info.key, mint_ore_info.key)?;
}
// Mint hash tokens to market.
mint_to_signed(
mint_hash_info,
market_hash_info,
block_info,
token_program,
10_000_000,
&[BLOCK, &id.to_le_bytes()],
)?;
// Burn mint authority.
set_authority_signed(
mint_hash_info,
block_info,
None,
spl_token_2022::instruction::AuthorityType::MintTokens,
token_program,
&[BLOCK, &id.to_le_bytes()],
)?;
Ok(()) Ok(())
} }

View File

@@ -0,0 +1,23 @@
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::<Block>(&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(())
}

View File

@@ -0,0 +1,46 @@
use ore_api::prelude::*;
use steel::*;
/// Withdraws stake.
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 [signer_info, miner_info, recipient_info, treasury_info, treasury_tokens_info, token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let miner = miner_info
.as_account_mut::<Miner>(&ore_api::ID)?
.assert_mut(|m| m.authority == *signer_info.key)?;
recipient_info.as_associated_token_account(&signer_info.key, &MINT_ADDRESS)?;
let treasury = treasury_info.as_account_mut::<Treasury>(&ore_api::ID)?;
treasury_tokens_info.as_associated_token_account(&treasury_info.key, &MINT_ADDRESS)?;
token_program.is_program(&spl_token::ID)?;
// Update account state.
miner.stake -= amount;
treasury.total_stake -= amount;
// Asset miner has enough stake to cover the withdrawal.
assert!(
miner.stake >= miner.deployed,
"Withdrawal cannot reduce capacity below deployment levels."
);
// Execute transfer.
transfer_signed(
signer_info,
treasury_tokens_info,
recipient_info,
token_program,
amount,
&[TREASURY],
)?;
Ok(())
}