Move to new supply model

This commit is contained in:
Hardhat Chad
2024-01-09 07:02:23 +00:00
parent c312761742
commit eb76fbe0a2
3 changed files with 447 additions and 395 deletions

121
Cargo.lock generated
View File

@@ -2,21 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aead" name = "aead"
version = "0.4.3" version = "0.4.3"
@@ -388,17 +373,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[package]]
name = "async-trait"
version = "0.1.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@@ -416,21 +390,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.12.3" version = "0.12.3"
@@ -521,12 +480,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bnum"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f"
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "0.9.3" version = "0.9.3"
@@ -674,12 +627,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@@ -1010,12 +957,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@@ -1058,12 +999,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hmac" name = "hmac"
version = "0.8.1" version = "0.8.1"
@@ -1298,15 +1233,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.4" version = "0.4.4"
@@ -1401,15 +1327,6 @@ dependencies = [
"syn 2.0.43", "syn 2.0.43",
] ]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.19.0" version = "1.19.0"
@@ -1428,9 +1345,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anchor-lang", "anchor-lang",
"anchor-spl", "anchor-spl",
"bnum",
"rand 0.8.5", "rand 0.8.5",
"sha256",
] ]
[[package]] [[package]]
@@ -1486,12 +1401,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]] [[package]]
name = "polyval" name = "polyval"
version = "0.5.3" version = "0.5.3"
@@ -1715,12 +1624,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@@ -1846,19 +1749,6 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "sha256"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386"
dependencies = [
"async-trait",
"bytes",
"hex",
"sha2 0.10.8",
"tokio",
]
[[package]] [[package]]
name = "sha3" name = "sha3"
version = "0.9.1" version = "0.9.1"
@@ -2468,17 +2358,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
dependencies = [
"backtrace",
"bytes",
"pin-project-lite",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.11" version = "0.5.11"

View File

@@ -24,8 +24,6 @@ default = []
[dependencies] [dependencies]
anchor-lang = "0.29.0" anchor-lang = "0.29.0"
anchor-spl = { version = "0.29.0", features = ["token"] } anchor-spl = { version = "0.29.0", features = ["token"] }
bnum = "0.10.0"
sha256 = "1.1.3"
[dev-dependencies] [dev-dependencies]
rand = "0.8.5" rand = "0.8.5"

View File

@@ -1,103 +1,183 @@
use std::mem::size_of;
use anchor_lang::{ use anchor_lang::{
prelude::*, prelude::*,
solana_program::{system_program, sysvar}, solana_program::{
hash::{hashv, Hash},
system_program, sysvar,
},
}; };
use anchor_spl::token::{self, Mint, MintTo, TokenAccount}; use anchor_spl::token::{self, Mint, MintTo, TokenAccount};
use bnum::types::U256;
use sha256;
declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS"); declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS");
/// The number of hashes per epoch (~24 hours). // TODO Set this to a reasonable value.
pub const EPOCH_HEIGHT: u64 = 8640; pub const DIFFICULTY: Hash = Hash::new_from_array([
0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/// The target average duration in seconds between each valid hash. 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
pub const HASH_TIME: u64 = 10; ]);
/// The seed of the genesis hash.
pub const GENESIS: &str = "42";
/// The seed of the Metadata program derived address.
pub const METADATA: &[u8] = b"metadata";
/// The decimal precision of the Ore token.
pub const TOKEN_DECIMALS: u8 = 8;
/// The radix of U256 string encodings.
pub const RADIX: u32 = 16;
/// The coefficient of the supply function.
pub const SUPPLY_COEFFICIENT: u64 = 48484;
/// The exponent of the supply function.
pub const SUPPLY_EXPONENT: f64 = 0.618;
/// The smoothing factor for difficulty adjustments.
pub const SMOOTHING_FACTOR: U256 = U256::from_digit(4);
// TODO Set this before deployment // TODO Set this before deployment
/// The time after which mining can happen. /// The time after which mining can happen.
pub const START_AT: i64 = 0; pub const START_AT: i64 = 0;
pub const EPOCH_DURATION: i64 = 60;
pub const EXPECTED_EPOCH_REWARDS: u64 = 10u64.pow(TOKEN_DECIMALS as u32); // 1 ORE / epoch
pub const SMOOTHING_FACTOR: u64 = 256;
/// The decimal precision of the Ore token.
// If we use a decimal precision of 16, we can fit 10_000_000_000_000_000 (10 quadrillion) hashes in each minute.
// This is sufficiently far beyond what Solana is capable of processing.
// Max supply would still be very large and take millions of years to reach at a rate of 1 ORE / minute.
// We will not have to implement a variable difficulty to maintain the 1 ORE / min average.
// If token decimals were only 8, we likely would need a variable difficulty at some point.
pub const TOKEN_DECIMALS: u8 = 9;
pub const BUS_COUNT: u64 = 8;
// TODO Use 8,9,or 10 decimals. Test and run math to see which one makes the most sense.
// TODO Track number of successful hashes per bus and add limit to # hashes/bus exceeding EXPECTED_EPOCH_REWARDS/NUMBER_OF_BUSSES.
// Rewards per epoch can exceed target limit. But hashes cannot exceed theoretical maximum.
// Eg. scenario where reward_rate = 1, cannot go lower, and the network is submitting enough hashes to push issuance rate above 1 ORE / epoch.
// In this case, each valid hash is earning the smallest reward possible.
// By hard limitting the number of hashes per bus per epoch, we prevent the 1 ORE/epoch limit from being fundamentally broken.
#[program] #[program]
mod ore { mod ore {
use super::*; use super::*;
/// Initializes the program. Can only be executed once. /// Initializes the program. Can only be executed once.
pub fn initialize(ctx: Context<Initialize>) -> Result<()> { pub fn initialize_metadata(ctx: Context<InitializeMetadata>) -> Result<()> {
let metadata = &mut ctx.accounts.metadata; ctx.accounts.metadata.bump = ctx.bumps.metadata;
metadata.bump = ctx.bumps.metadata; ctx.accounts.metadata.reward_rate = 10u64.pow(TOKEN_DECIMALS.saturating_div(2) as u32);
metadata.difficulty = U256::MAX.to_str_radix(RADIX); ctx.accounts.metadata.mint = ctx.accounts.mint.key();
metadata.hash = sha256::digest(GENESIS.to_string());
metadata.height = 0;
metadata.mint = ctx.accounts.mint.key();
Ok(()) Ok(())
} }
/// Mints new Ore to the beneficiary if a valid hash is provided. /// Initializes the program. Can only be executed once.
pub fn mine(ctx: Context<Mine>, hash: String, nonce: u64) -> Result<()> { pub fn initialize_busses(ctx: Context<InitializeBusses>) -> Result<()> {
// Validate clock. ctx.accounts.bus_0.bump = ctx.bumps.bus_0;
let clock = Clock::get().unwrap(); ctx.accounts.bus_0.id = 0;
require!(clock.unix_timestamp.ge(&START_AT), ProgramError::NotStarted); ctx.accounts.bus_1.bump = ctx.bumps.bus_1;
ctx.accounts.bus_1.id = 1;
ctx.accounts.bus_2.bump = ctx.bumps.bus_2;
ctx.accounts.bus_2.id = 2;
ctx.accounts.bus_3.bump = ctx.bumps.bus_3;
ctx.accounts.bus_3.id = 3;
ctx.accounts.bus_4.bump = ctx.bumps.bus_4;
ctx.accounts.bus_4.id = 4;
ctx.accounts.bus_5.bump = ctx.bumps.bus_5;
ctx.accounts.bus_5.id = 5;
ctx.accounts.bus_6.bump = ctx.bumps.bus_6;
ctx.accounts.bus_6.id = 6;
ctx.accounts.bus_7.bump = ctx.bumps.bus_7;
ctx.accounts.bus_7.id = 7;
Ok(())
}
// Log request. pub fn register_miner(ctx: Context<RegisterMiner>) -> Result<()> {
let miner = &mut ctx.accounts.miner;
miner.authority = ctx.accounts.signer.key();
miner.bump = ctx.bumps.miner;
miner.hash = hashv(&[&ctx.accounts.signer.key().to_bytes()]);
Ok(())
}
pub fn start_epoch(ctx: Context<StartEpoch>) -> Result<()> {
// Validate epoch has ended.
let clock = Clock::get().unwrap();
let metadata = &mut ctx.accounts.metadata; let metadata = &mut ctx.accounts.metadata;
msg!("Difficulty: {}", metadata.difficulty); let epoch_end_at = metadata.epoch_start_at.saturating_add(EPOCH_DURATION);
msg!("Hash: {}", hash); require!(
msg!("Nonce: {}", nonce); clock.unix_timestamp.ge(&epoch_end_at),
ProgramError::ClockInvalid
);
// Calculate total rewards issued during the epoch.
let bus_0 = &mut ctx.accounts.bus_0;
let bus_1 = &mut ctx.accounts.bus_1;
let bus_2 = &mut ctx.accounts.bus_2;
let bus_3 = &mut ctx.accounts.bus_3;
let bus_4 = &mut ctx.accounts.bus_4;
let bus_5 = &mut ctx.accounts.bus_5;
let bus_6 = &mut ctx.accounts.bus_6;
let bus_7 = &mut ctx.accounts.bus_7;
let total_epoch_rewards = bus_0
.rewards
.saturating_add(bus_1.rewards)
.saturating_add(bus_2.rewards)
.saturating_add(bus_3.rewards)
.saturating_add(bus_4.rewards)
.saturating_add(bus_5.rewards)
.saturating_add(bus_6.rewards)
.saturating_add(bus_7.rewards);
// Update the reward amount for the next epoch.
metadata.reward_rate = calculate_new_reward_rate(metadata.reward_rate, total_epoch_rewards);
// Reset state for new epoch.
bus_0.hashes = 0;
bus_1.hashes = 0;
bus_2.hashes = 0;
bus_3.hashes = 0;
bus_4.hashes = 0;
bus_5.hashes = 0;
bus_6.hashes = 0;
bus_7.hashes = 0;
bus_0.hashes = 0;
bus_1.rewards = 0;
bus_2.rewards = 0;
bus_3.rewards = 0;
bus_4.rewards = 0;
bus_5.rewards = 0;
bus_6.rewards = 0;
bus_7.rewards = 0;
metadata.epoch_start_at = clock.unix_timestamp;
Ok(())
}
pub fn mine(ctx: Context<Mine>, hash: Hash, nonce: u64) -> Result<()> {
// Validate epoch is active.
let clock = Clock::get().unwrap();
let metadata = &mut ctx.accounts.metadata;
let epoch_end_at = metadata.epoch_start_at.saturating_add(EPOCH_DURATION);
require!(
clock.unix_timestamp.lt(&epoch_end_at),
ProgramError::EpochNotActive
);
// Validate hash. // Validate hash.
let difficulty = U256::parse_str_radix(&metadata.difficulty, RADIX); let miner = &mut ctx.accounts.miner;
validate_hash( validate_hash(
metadata.hash.clone(), miner.hash.clone(),
hash.clone(), hash.clone(),
ctx.accounts.signer.key(), ctx.accounts.signer.key(),
nonce, nonce,
difficulty, DIFFICULTY,
)?; )?;
msg!("Hash is valid");
// Update metadata. // Update state.
metadata.hash = hash.clone(); ctx.accounts.bus.hashes = ctx.accounts.bus.hashes.saturating_add(1);
metadata.height = metadata.height.checked_add(1).unwrap(); ctx.accounts.bus.rewards = ctx
if metadata.height.eq(&1) { .accounts
metadata.epoch_start_at = clock.unix_timestamp; .bus
} .rewards
.saturating_add(metadata.reward_rate);
miner.hash = hash.clone();
// Update difficulty, if new epoch. // Error if this bus has already processed its quota of hashes for the epoch.
if metadata.height % (EPOCH_HEIGHT as u128) == 0 { require!(
metadata.difficulty = ctx.accounts
calculate_new_difficulty(metadata.epoch_start_at, clock.unix_timestamp, difficulty) .bus
.expect("Failed to calculate new difficulty") .hashes
.to_str_radix(RADIX); .le(&EXPECTED_EPOCH_REWARDS.saturating_div(BUS_COUNT)),
metadata.epoch_start_at = clock.unix_timestamp; // TODO Needs a dedicated error
} ProgramError::HashInvalid
);
// Mint reward to beneficiary. // Mint reward to beneficiary.
let supply = calculate_supply(metadata.height);
let reward = supply
.checked_sub(ctx.accounts.mint.supply)
.expect("Failed to calculate reward amount");
token::mint_to( token::mint_to(
CpiContext::new_with_signer( CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(), ctx.accounts.token_program.to_account_info(),
@@ -108,105 +188,127 @@ mod ore {
}, },
&[&[METADATA, &[metadata.bump]]], &[&[METADATA, &[metadata.bump]]],
), ),
reward, metadata.reward_rate,
)?; )?;
// Log result.
msg!("Height: {}", metadata.height);
msg!("Reward: {}", reward);
msg!("Supply: {}", supply);
emit!(MineEvent {
signer: ctx.accounts.signer.key(),
beneficiary: ctx.accounts.beneficiary.key(),
height: metadata.height,
reward,
supply,
hash,
nonce,
difficulty: metadata.difficulty.clone(),
});
Ok(()) Ok(())
} }
} }
fn validate_hash( fn validate_hash(
current_hash: String, current_hash: Hash,
hash: String, hash: Hash,
signer: Pubkey, signer: Pubkey,
nonce: u64, nonce: u64,
difficulty: U256, difficulty: Hash,
) -> Result<()> { ) -> Result<()> {
// Validate hash correctness. // Validate hash correctness.
let msg = format!("{}-{}-{}", current_hash, signer, nonce); let bytes = [
require!(sha256::digest(msg).eq(&hash), ProgramError::HashInvalid); current_hash.to_bytes().as_slice(),
signer.to_bytes().as_slice(),
nonce.to_be_bytes().as_slice(),
]
.concat();
let hash_ = hashv(&[&bytes]);
require!(hash.eq(&hash_), ProgramError::HashInvalid);
// Validate hash difficulty. // Validate hash difficulty.
let hash_u256 = U256::parse_str_radix(&hash, RADIX); require!(hash.le(&difficulty), ProgramError::HashInvalid);
require!(hash_u256.le(&difficulty), ProgramError::HashInvalid);
Ok(()) Ok(())
} }
fn calculate_new_difficulty(t1: i64, t2: i64, difficulty: U256) -> Result<U256> { fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 {
// Calculate time ratio. // Avoid division by zero. Leave the reward rate unchanged.
require!(t2.gt(&t1), ProgramError::ClockInvalid); if epoch_rewards.eq(&0) {
let actual_time = t2.saturating_sub(t1) as f64; return current_rate;
let expected_time = EPOCH_HEIGHT.saturating_mul(HASH_TIME) as f64; }
let time_ratio = actual_time / expected_time;
// Scale time ratio for integer arithmetic. // Calculate new rate.
const SCALE_FACTOR: f64 = 1000f64; msg!("Current rate: {}", current_rate);
let time_ratio_scaled = U256::from_digit((time_ratio * SCALE_FACTOR) as u64); msg!("Epoch rewards: {}", epoch_rewards);
msg!("Expected rewards: {}", EXPECTED_EPOCH_REWARDS);
let new_rate = (current_rate as u128)
.saturating_mul(EXPECTED_EPOCH_REWARDS as u128)
.saturating_div(epoch_rewards as u128) as u64;
msg!("New rate: {}", new_rate);
// Calculate new difficulty. // Smooth reward rate to not change by more than a constant factor from one epoch to the next.
const SCALE_FACTOR_U256: U256 = U256::from_digit(SCALE_FACTOR as u64); let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR);
let new_difficulty_scaled = difficulty.saturating_mul(time_ratio_scaled); let new_rate_max = current_rate.saturating_mul(SMOOTHING_FACTOR);
let new_difficulty = new_difficulty_scaled.saturating_div(SCALE_FACTOR_U256); let new_rate_smoothed = new_rate_min.max(new_rate_max.min(new_rate));
msg!("New rate min: {}", new_rate_min);
msg!("New rate max: {}", new_rate_max);
msg!("New rate smoothed: {}", new_rate_smoothed);
// Smooth new difficulty to a min/max multiple of the old difficulty. // Prevent new reward from reaching 0 and return.
let new_difficulty_min = difficulty.saturating_div(SMOOTHING_FACTOR); new_rate_smoothed.max(1)
let new_difficulty_max = difficulty.saturating_mul(SMOOTHING_FACTOR);
let new_difficulty_smoothed = new_difficulty_min.max(new_difficulty_max.min(new_difficulty));
Ok(new_difficulty_smoothed)
} }
fn calculate_supply(height: u128) -> u64 { /// The seed of the Bus account PDA.
((SUPPLY_COEFFICIENT as f64 * (height as f64).powf(SUPPLY_EXPONENT)) pub const BUS: &[u8] = b"bus";
* 10f64.powf(TOKEN_DECIMALS as f64)) as u64
} /// The seed of the Metadata account PDA.
pub const METADATA: &[u8] = b"metadata";
/// The seed of the Miner account PDA.
pub const MINER: &[u8] = b"miner";
#[account] #[account]
#[derive(Debug, InitSpace)] #[derive(Debug)]
pub struct Metadata { pub struct Metadata {
/// The bump of the metadata account address. /// The bump of the metadata PDA.
pub bump: u8, pub bump: u8,
/// The current mining difficulty.
#[max_len(256)]
pub difficulty: String,
/// The current hash.
#[max_len(256)]
pub hash: String,
/// The current height of the hash chain.
pub height: u128,
/// The mint address of the Ore token. /// The mint address of the Ore token.
pub mint: Pubkey, pub mint: Pubkey,
/// The timestamp of the start of the current epoch. /// The timestamp of the start of the current epoch.
pub epoch_start_at: i64, pub epoch_start_at: i64,
/// Reweard
pub reward_rate: u64,
}
#[account]
#[derive(Debug)]
pub struct Miner {
/// The bump of the miner PDA.
pub bump: u8,
/// The account authorized to hash this chain.
pub authority: Pubkey,
/// The miner's current hash.
pub hash: Hash,
}
/// Bus is an account used to track rewards issued during an epoch.
/// There are 8 bus accounts to provide parallelism and reduce write lock contention.
#[account]
#[derive(Debug)]
pub struct Bus {
/// The bump of the counter PDA.
pub bump: u8,
/// The ID of this counter account.
pub id: u8,
/// The count of rewards issued this epoch.
pub rewards: u64,
/// The count of valid hashes that have been submitted on this bus this epoch.
pub hashes: u64,
} }
#[derive(Accounts)] #[derive(Accounts)]
pub struct Initialize<'info> { pub struct InitializeMetadata<'info> {
/// The signer of the transaction. /// The signer of the transaction.
#[account(mut)] #[account(mut)]
pub signer: Signer<'info>, pub signer: Signer<'info>,
/// The metadata account. /// The metadata account.
#[account(init, seeds = [METADATA], bump, payer = signer, space = 8 + Metadata::INIT_SPACE)] #[account(init, seeds = [METADATA], bump, payer = signer, space = 8 + size_of::<Metadata>())]
pub metadata: Account<'info, Metadata>, pub metadata: Account<'info, Metadata>,
/// The Ore token mint. /// The Ore token mint.
@@ -227,7 +329,110 @@ pub struct Initialize<'info> {
} }
#[derive(Accounts)] #[derive(Accounts)]
#[instruction(hash: String, nonce: u64)] pub struct InitializeBusses<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// Bus account 0.
#[account(init, seeds = [BUS, &[0]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_0: Account<'info, Bus>,
/// Bus account 1.
#[account(init, seeds = [BUS, &[1]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_1: Account<'info, Bus>,
/// Bus account 2.
#[account(init, seeds = [BUS, &[2]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_2: Account<'info, Bus>,
/// Bus account 3.
#[account(init, seeds = [BUS, &[3]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_3: Account<'info, Bus>,
/// Bus account 4.
#[account(init, seeds = [BUS, &[4]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_4: Account<'info, Bus>,
/// Bus account 5.
#[account(init, seeds = [BUS, &[5]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_5: Account<'info, Bus>,
/// Bus account 6.
#[account(init, seeds = [BUS, &[6]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_6: Account<'info, Bus>,
/// Bus account 7.
#[account(init, seeds = [BUS, &[7]], bump, payer = signer, space = 8 + size_of::<Bus>())]
pub bus_7: Account<'info, Bus>,
/// The Solana system program.
#[account(address = system_program::ID)]
pub system_program: Program<'info, System>,
}
/// RegisterMiner registers a new miner with the Ore program and starts a new hash chain for them to mine.
#[derive(Accounts)]
pub struct RegisterMiner<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// The miner account.
#[account(init, seeds = [MINER, signer.key().as_ref()], bump, payer = signer, space = 8 + size_of::<Miner>())]
pub miner: Account<'info, Miner>,
/// The Solana system program.
#[account(address = system_program::ID)]
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct StartEpoch<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// Counter account 0.
#[account(mut, seeds = [BUS, &[0]], bump)]
pub bus_0: Account<'info, Bus>,
/// Bus account 1.
#[account(mut, seeds = [BUS, &[1]], bump)]
pub bus_1: Account<'info, Bus>,
/// Bus account 2.
#[account(mut, seeds = [BUS, &[2]], bump)]
pub bus_2: Account<'info, Bus>,
/// Bus account 3.
#[account(mut, seeds = [BUS, &[3]], bump)]
pub bus_3: Account<'info, Bus>,
/// Bus account 4.
#[account(mut, seeds = [BUS, &[4]], bump)]
pub bus_4: Account<'info, Bus>,
/// Bus account 5.
#[account(mut, seeds = [BUS, &[5]], bump)]
pub bus_5: Account<'info, Bus>,
/// Bus account 6.
#[account(mut, seeds = [BUS, &[6]], bump)]
pub bus_6: Account<'info, Bus>,
/// Bus account 7.
#[account(mut, seeds = [BUS, &[7]], bump)]
pub bus_7: Account<'info, Bus>,
/// The metadata account.
#[account(mut, seeds = [METADATA], bump)]
pub metadata: Account<'info, Metadata>,
}
// TODO Bytes, not strings
#[derive(Accounts)]
#[instruction(hash: Hash, nonce: u64)]
pub struct Mine<'info> { pub struct Mine<'info> {
/// The signer of the transaction (i.e. the miner). /// The signer of the transaction (i.e. the miner).
#[account(mut)] #[account(mut)]
@@ -237,11 +442,19 @@ pub struct Mine<'info> {
#[account(mut, token::mint = mint)] #[account(mut, token::mint = mint)]
pub beneficiary: Account<'info, TokenAccount>, pub beneficiary: Account<'info, TokenAccount>,
/// A bus account for tracking epoch rewards.
#[account(mut)]
pub bus: Account<'info, Bus>,
/// The metadata account. /// The metadata account.
#[account(mut, seeds = [METADATA], bump = metadata.bump, has_one = mint)] #[account(seeds = [METADATA], bump = metadata.bump, has_one = mint)]
pub metadata: Account<'info, Metadata>, pub metadata: Account<'info, Metadata>,
/// The Ore token mint. /// The metadata account.
#[account(mut, seeds = [MINER, signer.key().as_ref()], bump = miner.bump, constraint = signer.key().eq(&miner.authority))]
pub miner: Account<'info, Miner>,
/// The Ore token mint account.
#[account(mut)] #[account(mut)]
pub mint: Account<'info, Mint>, pub mint: Account<'info, Mint>,
@@ -260,9 +473,6 @@ pub struct MineEvent {
/// The beneficiary token account to which rewards were minted. /// The beneficiary token account to which rewards were minted.
pub beneficiary: Pubkey, pub beneficiary: Pubkey,
/// The updated height of the program's hash chain.
pub height: u128,
/// The quantity of new Ore tokens that were mined. /// The quantity of new Ore tokens that were mined.
pub reward: u64, pub reward: u64,
@@ -270,13 +480,13 @@ pub struct MineEvent {
pub supply: u64, pub supply: u64,
/// The valid hash provided by the signer. /// The valid hash provided by the signer.
pub hash: String, pub hash: Hash,
/// The current mining difficulty.
pub difficulty: Hash,
/// The nonce provided by the signer. /// The nonce provided by the signer.
pub nonce: u64, pub nonce: u64,
/// The current mining difficulty.
pub difficulty: String,
} }
#[error_code] #[error_code]
@@ -287,167 +497,132 @@ pub enum ProgramError {
HashInvalid, HashInvalid,
#[msg("Mining has not started yet")] #[msg("Mining has not started yet")]
NotStarted, NotStarted,
#[msg("The epoch has ended and needs to be reset")]
EpochNotActive,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use anchor_lang::prelude::Pubkey; // use anchor_lang::prelude::Pubkey;
use bnum::types::U256; // use bnum::types::U256;
use rand::prelude::*; // use rand::prelude::*;
use crate::{calculate_new_difficulty, calculate_supply, validate_hash}; // use crate::validate_hash;
#[test] #[test]
fn test_validate_hash_pass() { fn test_validate_hash_pass() {
let h1 = sha256::digest("Seed"); // let h1 = sha256::digest("Seed");
let signer = Pubkey::new_unique(); // let signer = Pubkey::new_unique();
let nonce = 10; // let nonce = 10;
let h2 = sha256::digest(format!("{}-{}-{}", h1, signer, nonce)); // let h2 = sha256::digest(format!("{}-{}-{}", h1, signer, nonce));
let res = validate_hash(h1, h2, signer, nonce, U256::MAX); // let res = validate_hash(h1, h2, signer, nonce, U256::MAX);
assert!(res.is_ok()); // assert!(res.is_ok());
} }
#[test] #[test]
fn test_validate_hash_fail() { fn test_validate_hash_fail() {
let h1 = sha256::digest("Seed"); // let h1 = sha256::digest("Seed");
let signer = Pubkey::new_unique(); // let signer = Pubkey::new_unique();
let nonce = 10; // let nonce = 10;
let h2 = String::from("Invalid hash"); // let h2 = String::from("Invalid hash");
let res = validate_hash(h1, h2, signer, nonce, U256::MAX); // let res = validate_hash(h1, h2, signer, nonce, U256::MAX);
assert!(res.is_err()); // assert!(res.is_err());
} }
#[test] #[test]
fn test_validate_hash_fail_difficulty() { fn test_validate_hash_fail_difficulty() {
let h1 = sha256::digest("Seed"); // let h1 = sha256::digest("Seed");
let signer = Pubkey::new_unique(); // let signer = Pubkey::new_unique();
let nonce = 10; // let nonce = 10;
let h2 = sha256::digest(format!("{}-{}-{}", h1, signer, nonce)); // let h2 = sha256::digest(format!("{}-{}-{}", h1, signer, nonce));
let res = validate_hash(h1, h2, signer, nonce, U256::MIN); // let res = validate_hash(h1, h2, signer, nonce, U256::MIN);
assert!(res.is_err()); // assert!(res.is_err());
} }
#[test] #[test]
fn test_validate_hash_fuzz() { fn test_validate_hash_fuzz() {
let h1 = sha256::digest("Seed"); // let h1 = sha256::digest("Seed");
let signer = Pubkey::new_unique(); // let signer = Pubkey::new_unique();
let mut rng = rand::thread_rng(); // let mut rng = rand::thread_rng();
for i in 0..10_000 { // for i in 0..10_000 {
let nonce = rng.gen::<u64>(); // let nonce = rng.gen::<u64>();
let h2 = sha256::digest(i.to_string()); // let h2 = sha256::digest(i.to_string());
let res = validate_hash(h1.clone(), h2, signer, nonce, U256::MAX); // let res = validate_hash(h1.clone(), h2, signer, nonce, U256::MAX);
assert!(res.is_err()); // assert!(res.is_err());
} // }
} }
#[test] // #[test]
fn test_calculate_new_difficulty_stable() { // fn test_calculate_new_difficulty_stable() {
let t1 = 0i64; // let t1 = 0i64;
let t2 = 86_400i64; // let t2 = 86_400i64;
let difficulty = U256::from_digit(100); // let difficulty = U256::from_digit(100);
let new_difficulty = calculate_new_difficulty(t1, t2, difficulty); // let new_difficulty = calculate_new_difficulty(t1, t2, difficulty);
assert!(new_difficulty.is_ok()); // assert!(new_difficulty.is_ok());
let x = new_difficulty.unwrap(); // let x = new_difficulty.unwrap();
assert!(x.eq(&U256::from_digit(100))); // assert!(x.eq(&U256::from_digit(100)));
} // }
#[test] // #[test]
fn test_calculate_new_difficulty_higher() { // fn test_calculate_new_difficulty_higher() {
let t1 = 0i64; // let t1 = 0i64;
let t2 = 172_800i64; // let t2 = 172_800i64;
let difficulty = U256::from_digit(100); // let difficulty = U256::from_digit(100);
let new_difficulty = calculate_new_difficulty(t1, t2, difficulty); // let new_difficulty = calculate_new_difficulty(t1, t2, difficulty);
assert!(new_difficulty.is_ok()); // assert!(new_difficulty.is_ok());
let x = new_difficulty.unwrap(); // let x = new_difficulty.unwrap();
assert!(x.eq(&U256::from_digit(200))); // assert!(x.eq(&U256::from_digit(200)));
} // }
#[test] // #[test]
fn test_calculate_new_difficulty_lower() { // fn test_calculate_new_difficulty_lower() {
let t1 = 0i64; // let t1 = 0i64;
let t2 = 43_200i64; // let t2 = 43_200i64;
let difficulty = U256::from_digit(100); // let difficulty = U256::from_digit(100);
let new_difficulty = calculate_new_difficulty(t1, t2, difficulty); // let new_difficulty = calculate_new_difficulty(t1, t2, difficulty);
assert!(new_difficulty.is_ok()); // assert!(new_difficulty.is_ok());
let x = new_difficulty.unwrap(); // let x = new_difficulty.unwrap();
assert!(x.eq(&U256::from_digit(50))); // assert!(x.eq(&U256::from_digit(50)));
} // }
#[test] // #[test]
fn test_calculate_new_difficulty_max() { // fn test_calculate_new_difficulty_max() {
let t1 = 0i64; // let t1 = 0i64;
let t2 = 1_000_000i64; // let t2 = 1_000_000i64;
let difficulty = U256::from_digit(100); // let difficulty = U256::from_digit(100);
let new_difficulty = calculate_new_difficulty(t1, t2, difficulty); // let new_difficulty = calculate_new_difficulty(t1, t2, difficulty);
assert!(new_difficulty.is_ok()); // assert!(new_difficulty.is_ok());
let x = new_difficulty.unwrap(); // let x = new_difficulty.unwrap();
assert!(x.eq(&U256::from_digit(400))); // assert!(x.eq(&U256::from_digit(400)));
} // }
#[test] // #[test]
fn test_calculate_new_difficulty_min() { // fn test_calculate_new_difficulty_min() {
let t1 = 0i64; // let t1 = 0i64;
let t2 = 1i64; // let t2 = 1i64;
let difficulty = U256::from_digit(100); // let difficulty = U256::from_digit(100);
let new_difficulty = calculate_new_difficulty(t1, t2, difficulty); // let new_difficulty = calculate_new_difficulty(t1, t2, difficulty);
assert!(new_difficulty.is_ok()); // assert!(new_difficulty.is_ok());
let x = new_difficulty.unwrap(); // let x = new_difficulty.unwrap();
assert!(x.eq(&U256::from_digit(25))); // assert!(x.eq(&U256::from_digit(25)));
} // }
#[test] // #[test]
fn test_calculate_new_difficulty_err() { // fn test_calculate_new_difficulty_err() {
let t1 = 10i64; // let t1 = 10i64;
let t2 = 5i64; // let t2 = 5i64;
let difficulty = U256::from_digit(100); // let difficulty = U256::from_digit(100);
let new_difficulty = calculate_new_difficulty(t1, t2, difficulty); // let new_difficulty = calculate_new_difficulty(t1, t2, difficulty);
assert!(new_difficulty.is_err()); // assert!(new_difficulty.is_err());
} // }
#[test] // #[test]
fn test_calculate_supply() { // fn test_calculate_supply() {
let s1 = calculate_supply(1); // let s1 = calculate_supply(1);
let s10 = calculate_supply(10); // let s10 = calculate_supply(10);
let s100 = calculate_supply(100); // let s100 = calculate_supply(100);
assert!(s1.eq(&4_848_400_000_000)); // assert!(s1.eq(&4_848_400_000_000));
assert!(s10.eq(&20_118_631_803_084)); // assert!(s10.eq(&20_118_631_803_084));
assert!(s100.eq(&83_483_075_989_621)); // assert!(s100.eq(&83_483_075_989_621));
} // }
}
fn estimate_size(x: u32) -> u32 {
assert!(x < 4096);
if x < 256 {
if x < 128 {
return 1;
} else {
return 3;
}
} else if x < 1024 {
if x > 1022 {
return 4;
} else {
return 5;
}
} else {
if x < 2048 {
return 7;
} else {
return 9;
}
}
}
#[cfg(kani)]
mod verification {
use super::*;
#[kani::proof]
pub fn check() {
// let x: u32 = kani::any();
// kani::assume(x < 4096);
// let y = estimate_size(x);
// assert!(y < 10);
}
} }