Begin native rewrite without anchor

This commit is contained in:
Hardhat Chad
2024-02-13 07:17:12 +00:00
parent 1e9a11d1b3
commit cb30817447
11 changed files with 910 additions and 1126 deletions

View File

@@ -1,18 +0,0 @@
[toolchain]
[features]
seeds = false
skip-lint = false
[programs.localnet]
ore = "CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "Localnet"
wallet = "/home/ubuntu/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

339
Cargo.lock generated
View File

@@ -117,167 +117,6 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "anchor-attribute-access-control"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5f619f1d04f53621925ba8a2e633ba5a6081f2ae14758cbb67f38fd823e0a3e"
dependencies = [
"anchor-syn",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-attribute-account"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7f2a3e1df4685f18d12a943a9f2a7456305401af21a07c9fe076ef9ecd6e400"
dependencies = [
"anchor-syn",
"bs58 0.5.0",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-attribute-constant"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9423945cb55627f0b30903288e78baf6f62c6c8ab28fb344b6b25f1ffee3dca7"
dependencies = [
"anchor-syn",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-attribute-error"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ed12720033cc3c3bf3cfa293349c2275cd5ab99936e33dd4bf283aaad3e241"
dependencies = [
"anchor-syn",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-attribute-event"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eef4dc0371eba2d8c8b54794b0b0eb786a234a559b77593d6f80825b6d2c77a2"
dependencies = [
"anchor-syn",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-attribute-program"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b18c4f191331e078d4a6a080954d1576241c29c56638783322a18d308ab27e4f"
dependencies = [
"anchor-syn",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-derive-accounts"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de10d6e9620d3bcea56c56151cad83c5992f50d5960b3a9bebc4a50390ddc3c"
dependencies = [
"anchor-syn",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-derive-serde"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4e2e5be518ec6053d90a2a7f26843dbee607583c779e6c8395951b9739bdfbe"
dependencies = [
"anchor-syn",
"borsh-derive-internal 0.10.3",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-derive-space"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecc31d19fa54840e74b7a979d44bcea49d70459de846088a1d71e87ba53c419"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "anchor-lang"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35da4785497388af0553586d55ebdc08054a8b1724720ef2749d313494f2b8ad"
dependencies = [
"anchor-attribute-access-control",
"anchor-attribute-account",
"anchor-attribute-constant",
"anchor-attribute-error",
"anchor-attribute-event",
"anchor-attribute-program",
"anchor-derive-accounts",
"anchor-derive-serde",
"anchor-derive-space",
"arrayref",
"base64 0.13.1",
"bincode",
"borsh 0.10.3",
"bytemuck",
"getrandom 0.2.11",
"solana-program",
"thiserror",
]
[[package]]
name = "anchor-spl"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c4fd6e43b2ca6220d2ef1641539e678bfc31b6cc393cf892b373b5997b6a39a"
dependencies = [
"anchor-lang",
"solana-program",
"spl-associated-token-account",
"spl-token",
"spl-token-2022 0.9.0",
]
[[package]]
name = "anchor-syn"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9101b84702fed2ea57bd22992f75065da5648017135b844283a2f6d74f27825"
dependencies = [
"anyhow",
"bs58 0.5.0",
"heck 0.3.3",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2 0.10.8",
"syn 1.0.109",
"thiserror",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -778,15 +617,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
[[package]]
name = "bs58"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
dependencies = [
"tinyvec",
]
[[package]]
name = "bumpalo"
version = "3.14.0"
@@ -805,9 +635,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.14.0"
version = "1.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
dependencies = [
"bytemuck_derive",
]
@@ -1735,15 +1565,6 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -2420,11 +2241,11 @@ dependencies = [
[[package]]
name = "num_enum"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0"
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
dependencies = [
"num_enum_derive 0.7.1",
"num_enum_derive 0.7.2",
]
[[package]]
@@ -2441,9 +2262,9 @@ dependencies = [
[[package]]
name = "num_enum_derive"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [
"proc-macro-crate 2.0.1",
"proc-macro2",
@@ -2516,12 +2337,16 @@ dependencies = [
name = "ore"
version = "0.1.0"
dependencies = [
"anchor-lang",
"anchor-spl",
"bincode",
"bytemuck",
"num_enum 0.7.2",
"shank",
"solana-program",
"solana-program-test",
"solana-sdk",
"spl-associated-token-account",
"spl-token",
"static_assertions",
"tokio",
]
[[package]]
@@ -3402,6 +3227,52 @@ dependencies = [
"keccak",
]
[[package]]
name = "shank"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c9395612d493b69a522725eef78a095f199d43eeb847f4a4b77ec0cacab535"
dependencies = [
"shank_macro",
]
[[package]]
name = "shank_macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8abef069c02e15f62233679b1e71f3152fac10f90b3ff89ebbad6a25b7497754"
dependencies = [
"proc-macro2",
"quote",
"shank_macro_impl",
"shank_render",
"syn 1.0.109",
]
[[package]]
name = "shank_macro_impl"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d3d92bfcc6e08f882f2264d774d1a2f46dc36122adc1b76416ba6405a29a9c"
dependencies = [
"anyhow",
"proc-macro2",
"quote",
"serde",
"syn 1.0.109",
]
[[package]]
name = "shank_render"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a2ea9c6dd95ea311b3b81e63cf4e9c808ed04b098819e6d2c4b1a467d587203"
dependencies = [
"proc-macro2",
"quote",
"shank_macro_impl",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -3482,7 +3353,7 @@ dependencies = [
"Inflector",
"base64 0.21.5",
"bincode",
"bs58 0.4.0",
"bs58",
"bv",
"lazy_static",
"serde",
@@ -3491,7 +3362,7 @@ dependencies = [
"solana-config-program",
"solana-sdk",
"spl-token",
"spl-token-2022 1.0.0",
"spl-token-2022",
"spl-token-group-interface",
"spl-token-metadata-interface",
"thiserror",
@@ -3792,7 +3663,7 @@ dependencies = [
"ahash 0.8.6",
"blake3",
"block-buffer 0.10.4",
"bs58 0.4.0",
"bs58",
"bv",
"byteorder",
"cc",
@@ -3941,7 +3812,7 @@ dependencies = [
"blake3",
"borsh 0.10.3",
"borsh 0.9.3",
"bs58 0.4.0",
"bs58",
"bv",
"bytemuck",
"cc",
@@ -4127,7 +3998,7 @@ dependencies = [
"async-trait",
"base64 0.21.5",
"bincode",
"bs58 0.4.0",
"bs58",
"indicatif",
"log",
"reqwest",
@@ -4151,7 +4022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "131662e5eea4fa5fc88b01f07d9e430315c0976be848ba3994244249c5fb033a"
dependencies = [
"base64 0.21.5",
"bs58 0.4.0",
"bs58",
"jsonrpc-core",
"reqwest",
"semver",
@@ -4162,7 +4033,7 @@ dependencies = [
"solana-sdk",
"solana-transaction-status",
"solana-version",
"spl-token-2022 1.0.0",
"spl-token-2022",
"thiserror",
]
@@ -4267,7 +4138,7 @@ dependencies = [
"bincode",
"bitflags 2.4.1",
"borsh 0.10.3",
"bs58 0.4.0",
"bs58",
"bytemuck",
"byteorder",
"chrono",
@@ -4316,7 +4187,7 @@ version = "1.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60f58786e949f43b8c9b826fdfa5ad8586634b077ab04f989fb8e30535786712"
dependencies = [
"bs58 0.4.0",
"bs58",
"proc-macro2",
"quote",
"rustversion",
@@ -4455,7 +4326,7 @@ dependencies = [
"base64 0.21.5",
"bincode",
"borsh 0.10.3",
"bs58 0.4.0",
"bs58",
"lazy_static",
"log",
"serde",
@@ -4466,7 +4337,7 @@ dependencies = [
"spl-associated-token-account",
"spl-memo",
"spl-token",
"spl-token-2022 1.0.0",
"spl-token-2022",
"thiserror",
]
@@ -4638,7 +4509,7 @@ dependencies = [
"num-traits",
"solana-program",
"spl-token",
"spl-token-2022 1.0.0",
"spl-token-2022",
"thiserror",
]
@@ -4724,20 +4595,6 @@ dependencies = [
"syn 2.0.43",
]
[[package]]
name = "spl-tlv-account-resolution"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "062e148d3eab7b165582757453632ffeef490c02c86a48bfdb4988f63eefb3b9"
dependencies = [
"bytemuck",
"solana-program",
"spl-discriminator",
"spl-pod",
"spl-program-error",
"spl-type-length-value",
]
[[package]]
name = "spl-tlv-account-resolution"
version = "0.5.0"
@@ -4767,28 +4624,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "spl-token-2022"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4abf34a65ba420584a0c35f3903f8d727d1f13ababbdc3f714c6b065a686e86"
dependencies = [
"arrayref",
"bytemuck",
"num-derive 0.4.1",
"num-traits",
"num_enum 0.7.1",
"solana-program",
"solana-zk-token-sdk",
"spl-memo",
"spl-pod",
"spl-token",
"spl-token-metadata-interface",
"spl-transfer-hook-interface 0.3.0",
"spl-type-length-value",
"thiserror",
]
[[package]]
name = "spl-token-2022"
version = "1.0.0"
@@ -4799,7 +4634,7 @@ dependencies = [
"bytemuck",
"num-derive 0.4.1",
"num-traits",
"num_enum 0.7.1",
"num_enum 0.7.2",
"solana-program",
"solana-security-txt",
"solana-zk-token-sdk",
@@ -4808,7 +4643,7 @@ dependencies = [
"spl-token",
"spl-token-group-interface",
"spl-token-metadata-interface",
"spl-transfer-hook-interface 0.4.1",
"spl-transfer-hook-interface",
"spl-type-length-value",
"thiserror",
]
@@ -4840,22 +4675,6 @@ dependencies = [
"spl-type-length-value",
]
[[package]]
name = "spl-transfer-hook-interface"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051d31803f873cabe71aec3c1b849f35248beae5d19a347d93a5c9cccc5d5a9b"
dependencies = [
"arrayref",
"bytemuck",
"solana-program",
"spl-discriminator",
"spl-pod",
"spl-program-error",
"spl-tlv-account-resolution 0.4.0",
"spl-type-length-value",
]
[[package]]
name = "spl-transfer-hook-interface"
version = "0.4.1"
@@ -4868,7 +4687,7 @@ dependencies = [
"spl-discriminator",
"spl-pod",
"spl-program-error",
"spl-tlv-account-resolution 0.5.0",
"spl-tlv-account-resolution",
"spl-type-length-value",
]
@@ -4918,7 +4737,7 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"rustversion",
@@ -5466,12 +5285,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.11"

View File

@@ -1,17 +1,33 @@
[workspace]
members = [
"programs/*"
]
[package]
name = "ore"
version = "0.1.0"
description = "Ore is a cryptocurrency everyone can mine"
edition = "2021"
license = "Apache 2.0"
homepage = ""
repository = ""
documentation = ""
readme = "./README.md"
keywords = ["solana"]
# [package.metadata.kani.flags]
# default-unwind = 1
[lib]
crate-type = ["cdylib", "lib"]
name = "ore"
[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
[features]
no-entrypoint = []
default = []
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1
[dependencies]
bytemuck = "1.14.3"
num_enum = "0.7.2"
shank = "0.3.0"
solana-program = "^1.16"
spl-token = { version = "^4", features = ["no-entrypoint"] }
spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] }
static_assertions = "1.1.0"
[dev-dependencies]
solana-program-test = "^1.16"
solana-sdk = "^1.16"
tokio = { version = "1.35", features = ["full"] }

View File

@@ -1,32 +0,0 @@
[package]
name = "ore"
version = "0.1.0"
description = "Ore is a cryptocurrency everyone can mine"
edition = "2021"
license = "Apache 2.0"
homepage = ""
repository = ""
documentation = ""
readme = "./README.md"
keywords = ["solana"]
[lib]
crate-type = ["cdylib", "lib"]
name = "ore"
[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "0.29.0"
anchor-spl = { version = "0.29.0", features = ["token"] }
bincode = "1.3.3"
solana-program = "^1.16"
static_assertions = "1.1.0"
[dev-dependencies]
solana-program-test = "^1.16"

View File

@@ -1,2 +0,0 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@@ -1,795 +0,0 @@
use std::mem::size_of;
use anchor_lang::{
prelude::*,
solana_program::{
keccak::{hashv, Hash},
pubkey,
slot_hashes::SlotHash,
system_program, sysvar,
},
};
use anchor_spl::{
associated_token,
token::{self, Mint, MintTo, TokenAccount},
};
// TODO Test admin and difficulty adjustment functions!
declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS");
// TODO Set this before deployment
/// The unix timestamp after which mining is allowed.
pub const START_AT: i64 = 0;
/// The initial reward rate to payout in the first epoch.
pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32);
/// The initial hashing difficulty. The admin authority can update this in the future, if needed.
pub const INITIAL_DIFFICULTY: Hash = Hash::new_from_array([
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
]);
/// The mint address of the ORE token.
pub const TOKEN_MINT_ADDRESS: Pubkey = pubkey!("37TDfMS8NHpyhyCXBrY9m7rRrtj1f7TrFzD1iXqmTeUX");
/// The decimal precision of the ORE token.
/// Using SI prefixes, the smallest indivisible unit of ORE is a nanoORE.
/// 1 nanoORE = 0.000000001 ORE = one billionth of an ORE
pub const TOKEN_DECIMALS: u8 = 9;
/// One ORE token, denominated in units of nanoORE.
pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32);
/// The duration of an epoch, in units of seconds.
pub const EPOCH_DURATION: i64 = 60;
/// The target quantity of ORE to be mined per epoch, in units of nanoORE.
/// Inflation rate ≈ 1 ORE / epoch (min 0, max 2)
pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE;
/// The maximum quantity of ORE that can be mined per epoch, in units of nanoORE.
pub const MAX_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(2);
/// The quantity of ORE each bus is allowed to issue per epoch.
pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64);
/// The number of bus accounts, for parallelizing mine operations.
pub const BUS_COUNT: u8 = 8;
/// The smoothing factor for reward rate changes. The reward rate cannot change by more or less
/// than factor of this constant from one epoch to the next.
pub const SMOOTHING_FACTOR: u64 = 2;
// Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT.
static_assertions::const_assert!(
(MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS
);
#[program]
mod ore {
use super::*;
/// Initializes the treasury account. Can only be invoked once.
pub fn initialize_treasury(ctx: Context<InitializeTreasury>) -> Result<()> {
ctx.accounts.treasury.bump = ctx.bumps.treasury;
ctx.accounts.treasury.admin = ctx.accounts.signer.key();
ctx.accounts.treasury.difficulty = INITIAL_DIFFICULTY;
ctx.accounts.treasury.reward_rate = INITIAL_REWARD_RATE;
Ok(())
}
/// Initializes the bus accounts. Can only be invoked once.
pub fn initialize_busses(ctx: Context<InitializeBusses>) -> Result<()> {
ctx.accounts.bus_0.bump = ctx.bumps.bus_0;
ctx.accounts.bus_0.id = 0;
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(())
}
/// Initializes a proof account for a new miner. Can only invoked once per signer.
pub fn initialize_proof(ctx: Context<InitializeProof>) -> Result<()> {
ctx.accounts.proof.authority = ctx.accounts.signer.key();
ctx.accounts.proof.bump = ctx.bumps.proof;
ctx.accounts.proof.hash = hashv(&[&ctx.accounts.signer.key().to_bytes()]);
Ok(())
}
/// Updates the reward rate and starts the new epoch.
pub fn reset_epoch(ctx: Context<ResetEpoch>) -> Result<()> {
// Validate epoch has ended.
let clock = Clock::get().unwrap();
let treasury = &mut ctx.accounts.treasury;
let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION);
require!(
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_available_rewards = bus_0
.available_rewards
.saturating_add(bus_1.available_rewards)
.saturating_add(bus_2.available_rewards)
.saturating_add(bus_3.available_rewards)
.saturating_add(bus_4.available_rewards)
.saturating_add(bus_5.available_rewards)
.saturating_add(bus_6.available_rewards)
.saturating_add(bus_7.available_rewards);
let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_available_rewards);
// Update the reward amount for the next epoch.
treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards);
treasury.epoch_start_at = clock.unix_timestamp;
// Reset bus accounts.
bus_0.available_rewards = BUS_EPOCH_REWARDS;
bus_1.available_rewards = BUS_EPOCH_REWARDS;
bus_2.available_rewards = BUS_EPOCH_REWARDS;
bus_3.available_rewards = BUS_EPOCH_REWARDS;
bus_4.available_rewards = BUS_EPOCH_REWARDS;
bus_5.available_rewards = BUS_EPOCH_REWARDS;
bus_6.available_rewards = BUS_EPOCH_REWARDS;
bus_7.available_rewards = BUS_EPOCH_REWARDS;
// Top up treasury token account.
token::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
authority: treasury.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.treasury_tokens.to_account_info(),
},
&[&[TREASURY, &[treasury.bump]]],
),
total_epoch_rewards,
)?;
// Log data.
msg!("Epoch rewards: {:?}", total_epoch_rewards);
msg!("Reward rate: {:?}", treasury.reward_rate);
msg!("Supply: {:?}", ctx.accounts.mint.supply);
Ok(())
}
/// Distributes Ore tokens to the signer if a valid hash is provided.
pub fn mine(ctx: Context<Mine>, hash: Hash, nonce: u64) -> Result<()> {
// Validate epoch is active.
let clock = Clock::get().unwrap();
let treasury = &mut ctx.accounts.treasury;
let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION);
require!(
clock.unix_timestamp.lt(&epoch_end_at),
ProgramError::EpochNeedsReset
);
// Validate provided hash.
let proof = &mut ctx.accounts.proof;
validate_hash(
proof.hash.clone(),
hash.clone(),
ctx.accounts.signer.key(),
nonce,
treasury.difficulty,
)?;
// Update claimable rewards.
let bus = &mut ctx.accounts.bus;
require!(
bus.available_rewards.ge(&treasury.reward_rate),
ProgramError::BusInsufficientFunds
);
bus.available_rewards = bus.available_rewards.saturating_sub(treasury.reward_rate);
proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate);
// Hash most recent slot hash into the next challenge to prevent pre-mining attacks.
let slot_hash_bytes = &ctx.accounts.slot_hashes.data.borrow()[0..size_of::<SlotHash>()];
let slot_hash: SlotHash = bincode::deserialize(slot_hash_bytes).unwrap();
proof.hash = hashv(&[hash.as_ref(), slot_hash.1.as_ref()]);
// Update lifetime stats.
proof.total_hashes = proof.total_hashes.saturating_add(1);
proof.total_rewards = proof.total_rewards.saturating_add(1);
// Log data.
msg!("Reward rate: {:?}", treasury.reward_rate);
msg!("Claimable rewards: {:?}", proof.claimable_rewards);
msg!("Total hashes: {:?}", proof.total_hashes);
msg!("Total rewards: {:?}", proof.total_rewards);
Ok(())
}
pub fn claim(ctx: Context<Claim>, amount: u64) -> Result<()> {
// Validate claim is for an appropriate quantity of tokens.
let proof = &mut ctx.accounts.proof;
require!(
proof.claimable_rewards.ge(&amount),
ProgramError::ClaimTooLarge
);
// Update claimable amount.
proof.claimable_rewards = proof.claimable_rewards.saturating_sub(amount);
// Update lifetime status.
let treasury = &mut ctx.accounts.treasury;
treasury.total_claimed_rewards = treasury.total_claimed_rewards.saturating_add(amount);
// Distribute tokens from treasury to beneficiary.
let treasury_tokens = &ctx.accounts.treasury_tokens;
require!(
treasury_tokens.amount.ge(&amount),
ProgramError::TreasuryInsufficientFunds
);
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: treasury_tokens.to_account_info(),
to: ctx.accounts.beneficiary.to_account_info(),
authority: treasury.to_account_info(),
},
&[&[TREASURY, &[treasury.bump]]],
),
amount,
)?;
// Log data.
msg!("Claimable rewards: {:?}", proof.claimable_rewards);
msg!(
"Total claimed rewards: {:?}",
treasury.total_claimed_rewards
);
Ok(())
}
/// Updates the admin to a new value. Can only be invoked by the admin authority.
pub fn update_admin(ctx: Context<UpdateDifficulty>, new_admin: Pubkey) -> Result<()> {
ctx.accounts.treasury.admin = new_admin;
Ok(())
}
/// Updates the difficulty to a new value. Can only be invoked by the admin authority.
///
/// Ore subdivides into 1B units of indivisible nanoORE. If global hashpower increases to the
/// point where >1B valid hashes are being submitted per epoch, the Ore inflation rate could
/// be pushed steadily above 1 ORE/epoch. The protocol guarantees inflation can never exceed
/// 2 ORE/epoch, but it is the responsibility of the admin to adjust the mining difficulty
/// as needed to maintain the 1 ORE/epoch average.
///
/// It is worth noting that Solana today processes well below 1M real TPS or
/// (60 * 1,000,000) = 60,000,000 hashes per epoch. Even if every transaction on the network
/// were mine operation, this is still two orders of magnitude below the threshold where the
/// Ore inflation rate would be challenged. So in practice, Solana is likely to reach its
/// network saturation point long before the Ore inflation hits its boundary condition.
pub fn update_difficulty(ctx: Context<UpdateDifficulty>, new_difficulty: Hash) -> Result<()> {
ctx.accounts.treasury.difficulty = new_difficulty;
Ok(())
}
}
fn validate_hash(
current_hash: Hash,
hash: Hash,
signer: Pubkey,
nonce: u64,
difficulty: Hash,
) -> Result<()> {
// Validate hash correctness.
let hash_ = hashv(&[
current_hash.as_ref(),
signer.as_ref(),
nonce.to_be_bytes().as_slice(),
]);
require!(hash.eq(&hash_), ProgramError::HashInvalid);
// Validate hash difficulty.
require!(hash.le(&difficulty), ProgramError::HashInvalid);
Ok(())
}
fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 {
// Avoid division by zero. Leave the reward rate unchanged.
if epoch_rewards.eq(&0) {
return current_rate;
}
// Calculate new reward rate.
let new_rate = (current_rate as u128)
.saturating_mul(TARGET_EPOCH_REWARDS as u128)
.saturating_div(epoch_rewards as u128) as u64;
// Smooth reward rate to not change by more than a constant factor from one epoch to the next.
let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR);
let new_rate_max = current_rate.saturating_mul(SMOOTHING_FACTOR);
let new_rate_smoothed = new_rate_min.max(new_rate_max.min(new_rate));
// Prevent reward rate from dropping below 1 or exceeding BUS_EPOCH_REWARDS and return.
new_rate_smoothed.max(1).min(BUS_EPOCH_REWARDS)
}
/// The seed of the bus account PDA.
pub const BUS: &[u8] = b"bus";
/// The seed of the proof account PDA.
pub const PROOF: &[u8] = b"proof";
/// The seed of the treasury account PDA.
pub const TREASURY: &[u8] = b"treasury";
/// Bus is an account type used to track the number of processed hashes and issued rewards
/// during an epoch. There are 8 bus accounts to provide sufficient parallelism for mine ops
/// and reduce write lock contention.
#[account]
#[derive(Debug, PartialEq)]
pub struct Bus {
/// The bump of the bus account PDA.
pub bump: u8,
/// The ID of the bus account.
pub id: u8,
/// The quantity of rewards this bus can issue in the current epoch epoch.
pub available_rewards: u64,
}
/// Proof is an account type used to track a miner's hash chain.
#[account]
#[derive(Debug, PartialEq)]
pub struct Proof {
/// The bump of the proof account PDA.
pub bump: u8,
/// The account (i.e. miner) authorized to use this proof.
pub authority: Pubkey,
/// The quantity of tokens this miner may claim from the treasury.
pub claimable_rewards: u64,
/// The proof's current hash.
pub hash: Hash,
/// The total lifetime hashes provided by this miner.
pub total_hashes: u64,
/// The total lifetime rewards distributed to this miner.
pub total_rewards: u64,
}
/// Treasury is an account type used to track global program variables.
#[account]
#[derive(Debug, PartialEq)]
pub struct Treasury {
/// The bump of the treasury account PDA.
pub bump: u8,
/// The admin authority with permission to update the difficulty.
pub admin: Pubkey,
/// The hash difficulty.
pub difficulty: Hash,
/// The timestamp of the start of the current epoch.
pub epoch_start_at: i64,
/// The reward rate to payout to miners for submiting valid hashes.
pub reward_rate: u64,
/// The total lifetime claimed rewards.
pub total_claimed_rewards: u64,
}
#[derive(Accounts)]
pub struct InitializeTreasury<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// The Ore token mint.
#[account(init, address = TOKEN_MINT_ADDRESS, payer = signer, mint::decimals = TOKEN_DECIMALS, mint::authority = treasury)]
pub mint: Account<'info, Mint>,
/// The treasury account.
#[account(init, seeds = [TREASURY], bump, payer = signer, space = 8 + size_of::<Treasury>())]
pub treasury: Account<'info, Treasury>,
/// The treasury token account.
#[account(init, associated_token::mint = mint, associated_token::authority = treasury, payer = signer)]
pub treasury_tokens: Account<'info, TokenAccount>,
/// The Solana system program.
#[account(address = system_program::ID)]
pub system_program: Program<'info, System>,
/// The SPL token program.
#[account(address = anchor_spl::token::ID)]
pub token_program: Program<'info, token::Token>,
/// The SPL associated token program.
#[account(address = anchor_spl::associated_token::ID)]
pub associated_token_program: Program<'info, associated_token::AssociatedToken>,
/// The rent sysvar account.
#[account(address = sysvar::rent::ID)]
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct InitializeBusses<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// The treasury account.
#[account(seeds = [TREASURY], bump = treasury.bump)]
pub treasury: Account<'info, Treasury>,
/// The Ore token mint account.
#[account(address = TOKEN_MINT_ADDRESS)]
pub mint: Account<'info, Mint>,
/// 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>,
}
/// InitializeProof initializes a new proof account for a miner.
#[derive(Accounts)]
pub struct InitializeProof<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// The proof account.
#[account(init, seeds = [PROOF, signer.key().as_ref()], bump, payer = signer, space = 8 + size_of::<Proof>())]
pub proof: Account<'info, Proof>,
/// The Solana system program.
#[account(address = system_program::ID)]
pub system_program: Program<'info, System>,
}
// ResetEpoch adjusts the reward rate based on global hashpower and begins the new epoch.
#[derive(Accounts)]
pub struct ResetEpoch<'info> {
/// The signer of the transaction.
#[account(mut)]
pub signer: Signer<'info>,
/// Bus account 0.
#[account(mut, seeds = [BUS, &[0]], bump = bus_0.bump)]
pub bus_0: Box<Account<'info, Bus>>,
/// Bus account 1.
#[account(mut, seeds = [BUS, &[1]], bump = bus_1.bump)]
pub bus_1: Box<Account<'info, Bus>>,
/// Bus account 2.
#[account(mut, seeds = [BUS, &[2]], bump = bus_2.bump)]
pub bus_2: Box<Account<'info, Bus>>,
/// Bus account 3.
#[account(mut, seeds = [BUS, &[3]], bump = bus_3.bump)]
pub bus_3: Box<Account<'info, Bus>>,
/// Bus account 4.
#[account(mut, seeds = [BUS, &[4]], bump = bus_4.bump)]
pub bus_4: Box<Account<'info, Bus>>,
/// Bus account 5.
#[account(mut, seeds = [BUS, &[5]], bump = bus_5.bump)]
pub bus_5: Box<Account<'info, Bus>>,
/// Bus account 6.
#[account(mut, seeds = [BUS, &[6]], bump = bus_6.bump)]
pub bus_6: Box<Account<'info, Bus>>,
/// Bus account 7.
#[account(mut, seeds = [BUS, &[7]], bump = bus_7.bump)]
pub bus_7: Box<Account<'info, Bus>>,
/// The Ore token mint account.
#[account(mut, address = TOKEN_MINT_ADDRESS)]
pub mint: Account<'info, Mint>,
/// The treasury account.
#[account(mut, seeds = [TREASURY], bump = treasury.bump)]
pub treasury: Account<'info, Treasury>,
/// The treasury token account.
#[account(mut, associated_token::mint = mint, associated_token::authority = treasury)]
pub treasury_tokens: Account<'info, TokenAccount>,
/// The SPL token program.
#[account(address = anchor_spl::token::ID)]
pub token_program: Program<'info, token::Token>,
}
/// Mine distributes Ore to the beneficiary if the signer provides a valid hash.
#[derive(Accounts)]
#[instruction(hash: Hash, nonce: u64)]
pub struct Mine<'info> {
/// The signer of the transaction (i.e. the miner).
#[account(mut, address = proof.authority)]
pub signer: Signer<'info>,
/// A bus account.
#[account(mut, constraint = bus.id.lt(&BUS_COUNT) @ ProgramError::BusInvalid)]
pub bus: Account<'info, Bus>,
/// The proof account.
#[account(mut, seeds = [PROOF, signer.key().as_ref()], bump = proof.bump)]
pub proof: Account<'info, Proof>,
/// The treasury account.
#[account(seeds = [TREASURY], bump = treasury.bump)]
pub treasury: Account<'info, Treasury>,
/// The SPL token program.
#[account(address = anchor_spl::token::ID)]
pub token_program: Program<'info, token::Token>,
/// The slot hashes sysvar account.
/// CHECK: SlotHashes is too large to deserialize. Instead we manually verify the sysvar address and deserialize only the slice we need.
#[account(address = sysvar::slot_hashes::ID)]
pub slot_hashes: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(amount: u64)]
pub struct Claim<'info> {
/// The signer of the transaction (i.e. the miner).
#[account(mut, address = proof.authority)]
pub signer: Signer<'info>,
/// The beneficiary token account to distribute rewards to.
#[account(mut, token::mint = mint)]
pub beneficiary: Account<'info, TokenAccount>,
/// The Ore token mint account.
#[account(address = TOKEN_MINT_ADDRESS)]
pub mint: Account<'info, Mint>,
/// The proof account.
#[account(mut, seeds = [PROOF, signer.key().as_ref()], bump = proof.bump)]
pub proof: Account<'info, Proof>,
/// The treasury account.
#[account(mut, seeds = [TREASURY], bump = treasury.bump)]
pub treasury: Account<'info, Treasury>,
/// The treasury token account.
#[account(mut, associated_token::mint = mint, associated_token::authority = treasury)]
pub treasury_tokens: Account<'info, TokenAccount>,
/// The SPL token program.
#[account(address = anchor_spl::token::ID)]
pub token_program: Program<'info, token::Token>,
}
/// UpdateAdmin allows the admin to reassign the admin authority.
#[derive(Accounts)]
#[instruction(new_admin: Pubkey)]
pub struct UpdateAdmin<'info> {
/// The signer of the transaction (i.e. the admin).
#[account(mut)]
pub signer: Signer<'info>,
/// The treasury account.
#[account(mut, seeds = [TREASURY], bump = treasury.bump, constraint = treasury.admin.eq(&signer.key()) @ ProgramError::NotAuthorized)]
pub treasury: Account<'info, Treasury>,
}
/// UpdateDifficulty allows the admin to update the mining difficulty.
#[derive(Accounts)]
#[instruction(new_difficulty: Hash)]
pub struct UpdateDifficulty<'info> {
/// The signer of the transaction (i.e. the admin).
#[account(mut)]
pub signer: Signer<'info>,
/// The treasury account.
#[account(mut, seeds = [TREASURY], bump = treasury.bump, constraint = treasury.admin.eq(&signer.key()) @ ProgramError::NotAuthorized)]
pub treasury: Account<'info, Treasury>,
}
/// MineEvent logs revelant data about a successful Ore mining transaction.
#[event]
#[derive(Debug)]
pub struct MineEvent {
/// The signer of the transaction (i.e. the miner).
pub signer: Pubkey,
/// The beneficiary token account to which rewards were minted.
pub beneficiary: Pubkey,
/// The quantity of new Ore tokens that were mined.
pub reward: u64,
/// The current Ore token supply.
pub supply: u64,
/// The current mining difficulty.
pub difficulty: Hash,
/// The valid hash provided by the signer.
pub hash: Hash,
/// The nonce provided by the signer.
pub nonce: u64,
}
#[error_code]
pub enum ProgramError {
#[msg("The clock time is invalid")]
ClockInvalid,
#[msg("The hash is invalid")]
HashInvalid,
#[msg("Mining has not started yet")]
NotStarted,
#[msg("The epoch has ended and needs to be reset")]
EpochNeedsReset,
#[msg("An invalid bus account was provided")]
BusInvalid,
#[msg("This bus does not have enough tokens to pay the reward")]
BusInsufficientFunds,
#[msg("The signer is not authorized to perform this action")]
NotAuthorized,
#[msg("You cannot claim more tokens than are available")]
ClaimTooLarge,
#[msg("The treasury does not have enough tokens to honor the claim")]
TreasuryInsufficientFunds,
}
#[cfg(test)]
mod tests {
use anchor_lang::{
prelude::Pubkey,
solana_program::keccak::{hashv, Hash},
};
use crate::{calculate_new_reward_rate, validate_hash, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS};
#[test]
fn test_validate_hash_pass() {
let h1 = Hash::new_from_array([1; 32]);
let signer = Pubkey::new_unique();
let nonce = 10u64;
let difficulty = Hash::new_from_array([255; 32]);
let h2 = hashv(&[
h1.to_bytes().as_slice(),
signer.to_bytes().as_slice(),
nonce.to_be_bytes().as_slice(),
]);
let res = validate_hash(h1, h2, signer, nonce, difficulty);
assert!(res.is_ok());
}
#[test]
fn test_validate_hash_fail() {
let h1 = Hash::new_from_array([1; 32]);
let signer = Pubkey::new_unique();
let nonce = 10u64;
let difficulty = Hash::new_from_array([255; 32]);
let h2 = Hash::new_from_array([2; 32]);
let res = validate_hash(h1, h2, signer, nonce, difficulty);
assert!(res.is_err());
}
#[test]
fn test_validate_hash_fail_difficulty() {
let h1 = Hash::new_from_array([1; 32]);
let signer = Pubkey::new_unique();
let nonce = 10u64;
let difficulty = Hash::new_from_array([0; 32]);
let h2 = hashv(&[
h1.to_bytes().as_slice(),
signer.to_bytes().as_slice(),
nonce.to_be_bytes().as_slice(),
]);
let res = validate_hash(h1, h2, signer, nonce, difficulty);
assert!(res.is_err());
}
#[test]
fn test_calculate_new_reward_rate_stable() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS);
assert!(new_rate.eq(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_no_chage() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, 0);
assert!(new_rate.eq(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_lower() {
let current_rate = 1000;
let new_rate =
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_add(1_000_000));
assert!(new_rate.lt(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_higher() {
let current_rate = 1000;
let new_rate =
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_sub(1_000_000));
assert!(new_rate.gt(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_max_smooth() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, 1);
assert!(new_rate.eq(&current_rate.saturating_mul(SMOOTHING_FACTOR)));
}
#[test]
fn test_calculate_new_reward_rate_min_smooth() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, u64::MAX);
assert!(new_rate.eq(&current_rate.saturating_div(SMOOTHING_FACTOR)));
}
}

View File

@@ -1,6 +1,5 @@
[toolchain]
channel = "1.70.0"
# channel = "nightly"
components = [ "rustfmt", "rust-analyzer" ]
profile = "minimal"
targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
profile = "minimal"

103
src/instruction.rs Normal file
View File

@@ -0,0 +1,103 @@
use bytemuck::{Pod, Zeroable};
use num_enum::TryFromPrimitive;
use shank::ShankInstruction;
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)]
#[rustfmt::skip]
pub enum OreInstruction {
#[account(0, name = "ore_program", desc = "Ore program")]
#[account(1, name = "signer", desc = "Signer", signer)]
#[account(2, name = "bus_0", desc = "Ore bus account 0", writable)]
#[account(3, name = "bus_1", desc = "Ore bus account 1", writable)]
#[account(4, name = "bus_2", desc = "Ore bus account 2", writable)]
#[account(5, name = "bus_3", desc = "Ore bus account 3", writable)]
#[account(6, name = "bus_4", desc = "Ore bus account 4", writable)]
#[account(7, name = "bus_5", desc = "Ore bus account 5", writable)]
#[account(8, name = "bus_6", desc = "Ore bus account 6", writable)]
#[account(9, name = "bus_7", desc = "Ore bus account 7", writable)]
#[account(10, name = "mint", desc = "Ore token mint account", writable)]
#[account(11, name = "treasury", desc = "Ore treasury account", writable)]
#[account(12, name = "treasury_tokens", desc = "Ore treasury token account", writable)]
#[account(13, name = "token_program", desc = "SPL token program")]
Epoch = 0,
#[account(0, name = "ore_program", desc = "Ore program")]
#[account(1, name = "signer", desc = "Signer", signer)]
#[account(2, name = "proof", desc = "Ore miner proof account", writable)]
#[account(3, name = "system_program", desc = "Solana system program")]
Proof = 1,
#[account(0, name = "ore_program", desc = "Ore program")]
#[account(1, name = "signer", desc = "Signer", signer)]
#[account(2, name = "bus", desc = "Ore bus account", writable)]
#[account(3, name = "proof", desc = "Ore miner proof account", writable)]
#[account(4, name = "treasury", desc = "Ore treasury account")]
#[account(5, name = "token_program", desc = "SPL token program")]
#[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")]
Mine = 2,
#[account(0, name = "ore_program", desc = "Ore program")]
#[account(1, name = "signer", desc = "Signer", signer)]
#[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)]
#[account(3, name = "mint", desc = "Ore token mint account")]
#[account(4, name = "proof", desc = "Ore miner proof account", writable)]
#[account(5, name = "treasury", desc = "Ore treasury account", writable)]
#[account(6, name = "treasury_tokens", desc = "Ore treasury token account", writable)]
#[account(7, name = "token_program", desc = "SPL token program")]
Claim = 3,
#[account(0, name = "ore_program", desc = "Ore program")]
#[account(1, name = "admin", desc = "Admin signer", signer)]
#[account(2, name = "bus_0", desc = "Ore bus account 0", writable)]
#[account(3, name = "bus_1", desc = "Ore bus account 1", writable)]
#[account(4, name = "bus_2", desc = "Ore bus account 2", writable)]
#[account(5, name = "bus_3", desc = "Ore bus account 3", writable)]
#[account(6, name = "bus_4", desc = "Ore bus account 4", writable)]
#[account(7, name = "bus_5", desc = "Ore bus account 5", writable)]
#[account(8, name = "bus_6", desc = "Ore bus account 6", writable)]
#[account(9, name = "bus_7", desc = "Ore bus account 7", writable)]
#[account(10, name = "mint", desc = "Ore token mint account")]
#[account(11, name = "treasury", desc = "Ore treasury account")]
#[account(12, name = "treasury_tokens", desc = "Ore treasury token account", writable)]
#[account(13, name = "system_program", desc = "Solana system program")]
#[account(14, name = "token_program", desc = "SPL token program")]
#[account(15, name = "rent", desc = "Solana rent sysvar")]
Initialize = 100,
// #[account(15, name = "associated_token_program", desc = "SPL associated token program")]
// TODO
// #[account(0, name = "ore_program", desc = "Ore program")]
// UpdateAdmin = 102,
// TODO
// #[account(0, name = "ore_program", desc = "Ore program")]
// UpdateDifficulty = 103,
}
impl OreInstruction {
pub fn to_vec(&self) -> Vec<u8> {
vec![*self as u8]
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct InitializeArgs {
pub bus_0_bump: u8,
pub bus_1_bump: u8,
pub bus_2_bump: u8,
pub bus_3_bump: u8,
pub bus_4_bump: u8,
pub bus_5_bump: u8,
pub bus_6_bump: u8,
pub bus_7_bump: u8,
pub mint_bump: u8,
pub treasury_bump: u8,
}
impl InitializeArgs {
pub fn to_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
}

514
src/lib.rs Normal file
View File

@@ -0,0 +1,514 @@
pub mod instruction;
mod loaders;
use std::mem::size_of;
use bytemuck::{Pod, Zeroable};
use solana_program::program_pack::Pack;
use solana_program::{self, sysvar};
use solana_program::{
account_info::{next_account_info, AccountInfo},
declare_id,
entrypoint::ProgramResult,
keccak::{hashv, Hash},
program_error::ProgramError,
pubkey,
pubkey::Pubkey,
rent::Rent,
system_program,
sysvar::Sysvar,
};
use instruction::*;
use loaders::*;
use spl_token::state::Mint;
// TODO Test admin and difficulty adjustment functions
// TODO Use more decimals?
declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS");
#[cfg(not(feature = "no-entrypoint"))]
solana_program::entrypoint!(process_instruction);
// TODO Set this before deployment
/// The unix timestamp after which mining is allowed.
pub const START_AT: i64 = 0;
/// The initial reward rate to payout in the first epoch.
pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32);
/// The initial hashing difficulty. The admin authority can update this in the future, if needed.
pub const INITIAL_DIFFICULTY: Hash = Hash::new_from_array([
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
]);
/// The mint address of the ORE token.
pub const MINT_ADDRESS: Pubkey = pubkey!("37TDfMS8NHpyhyCXBrY9m7rRrtj1f7TrFzD1iXqmTeUX");
/// The decimal precision of the ORE token.
/// Using SI prefixes, the smallest indivisible unit of ORE is a nanoORE.
/// 1 nanoORE = 0.000000001 ORE = one billionth of an ORE
pub const TOKEN_DECIMALS: u8 = 9;
/// One ORE token, denominated in units of nanoORE.
pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32);
/// The duration of an epoch, in units of seconds.
pub const EPOCH_DURATION: i64 = 60;
/// The target quantity of ORE to be mined per epoch, in units of nanoORE.
/// Inflation rate ≈ 1 ORE / epoch (min 0, max 2)
pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE;
/// The maximum quantity of ORE that can be mined per epoch, in units of nanoORE.
pub const MAX_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(2);
/// The quantity of ORE each bus is allowed to issue per epoch.
pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64);
/// The number of bus accounts, for parallelizing mine operations.
pub const BUS_COUNT: usize = 8;
/// The smoothing factor for reward rate changes. The reward rate cannot change by more or less
/// than factor of this constant from one epoch to the next.
pub const SMOOTHING_FACTOR: u64 = 2;
// Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT.
static_assertions::const_assert!(
(MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS
);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
let (tag, data) = data
.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;
let ix = OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))?;
match ix {
OreInstruction::Epoch => process_epoch(program_id, accounts, data)?,
OreInstruction::Proof => process_proof(program_id, accounts, data)?,
OreInstruction::Mine => process_mine(program_id, accounts, data)?,
OreInstruction::Claim => process_claim(program_id, accounts, data)?,
OreInstruction::Initialize => process_initialize(program_id, accounts, data)?,
}
Ok(())
}
fn process_epoch<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
// TODO
Ok(())
}
fn process_proof<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
// TODO
Ok(())
}
fn process_mine<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
// TODO
Ok(())
}
fn process_claim<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
// TODO
Ok(())
}
fn process_initialize<'a, 'info>(
_program_id: &Pubkey,
accounts: &'a [AccountInfo<'info>],
data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let args = bytemuck::try_from_bytes::<InitializeArgs>(data)
.or(Err(ProgramError::InvalidInstructionData))?;
// Account 1: Signer
let signer = load_signer(next_account_info(accounts_iter)?)?;
// Accounts 2-9: Busses
#[rustfmt::skip]
let busses = vec![
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[0], &[args.bus_0_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[1], &[args.bus_1_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[2], &[args.bus_2_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[3], &[args.bus_3_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[4], &[args.bus_4_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[5], &[args.bus_5_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[6], &[args.bus_6_bump]])?,
load_uninitialized_pda(next_account_info(accounts_iter)?, &[BUS, &[7], &[args.bus_7_bump]])?,
];
// Account 10: Mint
#[rustfmt::skip]
let mint = load_uninitialized_pda(next_account_info(accounts_iter)?, &[MINT, &[args.mint_bump]])?;
// Account 11: Treasury
#[rustfmt::skip]
let treasury_account_info = load_uninitialized_pda(next_account_info(accounts_iter)?, &[TREASURY, &[args.treasury_bump]])?;
// Account 12: Treasury tokens
let treasury_tokens = load_uninitialized_account(next_account_info(accounts_iter)?)?;
// Account 13: System program
let system_program = load_account(next_account_info(accounts_iter)?, system_program::id())?;
// Account 14: Token program
let token_program = load_account(next_account_info(accounts_iter)?, spl_token::id())?;
// Account 15: Rent sysvar
let rent_sysvar = load_account(next_account_info(accounts_iter)?, sysvar::rent::id())?;
// Initialize bus accounts
let bus_bumps = vec![
args.bus_0_bump,
args.bus_1_bump,
args.bus_2_bump,
args.bus_3_bump,
args.bus_4_bump,
args.bus_5_bump,
args.bus_6_bump,
args.bus_7_bump,
];
for i in 0..BUS_COUNT {
create_pda(
busses[i],
&crate::id(),
size_of::<Bus>(),
&[BUS, &[i as u8], &[bus_bumps[i]]],
system_program,
signer,
)?;
busses[i].try_borrow_mut_data()?.copy_from_slice(
Bus {
bump: bus_bumps[i] as u32,
id: i as u32,
available_rewards: 0,
}
.to_bytes(),
);
}
// Initialize treasury
create_pda(
treasury_account_info,
&crate::id(),
size_of::<Treasury>(),
&[TREASURY, &[args.treasury_bump]],
system_program,
signer,
)?;
let mut treasury_data = treasury_account_info.data.borrow_mut();
let mut treasury = bytemuck::try_from_bytes_mut::<Treasury>(&mut treasury_data).unwrap();
treasury.bump = args.treasury_bump as u64;
treasury.admin = *signer.key;
treasury.epoch_start_at = 0;
treasury.reward_rate = INITIAL_REWARD_RATE;
treasury.total_claimed_rewards = 0;
// Initialize mint
create_pda(
mint,
&spl_token::id(),
Mint::LEN,
&[MINT, &[args.mint_bump]],
system_program,
signer,
)?;
solana_program::program::invoke_signed(
&spl_token::instruction::initialize_mint(
&spl_token::id(),
mint.key,
treasury_account_info.key,
None,
TOKEN_DECIMALS,
)?,
&[
token_program.clone(),
mint.clone(),
treasury_account_info.clone(),
rent_sysvar.clone(),
],
&[&[MINT, &[args.mint_bump]]],
)?;
// TODO Initialize treasury token account
create_pda(
mint,
&spl_token::id(),
spl_token::state::Account::LEN,
&[MINT, &[args.mint_bump]],
system_program,
signer,
)?;
solana_program::program::invoke_signed(
&spl_token::instruction::initialize_mint(
&spl_token::id(),
mint.key,
treasury_account_info.key,
None,
TOKEN_DECIMALS,
)?,
&[
token_program.clone(),
mint.clone(),
treasury_account_info.clone(),
rent_sysvar.clone(),
],
&[&[MINT, &[args.mint_bump]]],
)?;
Ok(())
}
fn validate_hash(
current_hash: Hash,
hash: Hash,
signer: Pubkey,
nonce: u64,
difficulty: Hash,
) -> Result<(), ProgramError> {
// Validate hash correctness.
let hash_ = hashv(&[
current_hash.as_ref(),
signer.as_ref(),
nonce.to_be_bytes().as_slice(),
]);
if !hash.eq(&hash_) {
return Err(ProgramError::Custom(1));
}
// Validate hash difficulty.
if !hash.le(&difficulty) {
return Err(ProgramError::Custom(1));
}
Ok(())
}
fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 {
// Avoid division by zero. Leave the reward rate unchanged.
if epoch_rewards.eq(&0) {
return current_rate;
}
// Calculate new reward rate.
let new_rate = (current_rate as u128)
.saturating_mul(TARGET_EPOCH_REWARDS as u128)
.saturating_div(epoch_rewards as u128) as u64;
// Smooth reward rate to not change by more than a constant factor from one epoch to the next.
let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR);
let new_rate_max = current_rate.saturating_mul(SMOOTHING_FACTOR);
let new_rate_smoothed = new_rate_min.max(new_rate_max.min(new_rate));
// Prevent reward rate from dropping below 1 or exceeding BUS_EPOCH_REWARDS and return.
new_rate_smoothed.max(1).min(BUS_EPOCH_REWARDS)
}
/// Creates a new pda
#[inline(always)]
pub fn create_pda<'a, 'info>(
target_account: &'a AccountInfo<'info>,
owner: &Pubkey,
space: usize,
pda_seeds: &[&[u8]],
system_program: &'a AccountInfo<'info>,
payer: &'a AccountInfo<'info>,
) -> ProgramResult {
let rent = Rent::get()?;
solana_program::program::invoke_signed(
&solana_program::system_instruction::create_account(
payer.key,
target_account.key,
rent.minimum_balance(space as usize),
space as u64,
owner,
),
&[
payer.clone(),
target_account.clone(),
system_program.clone(),
],
&[pda_seeds],
)?;
Ok(())
}
/// The seed of the bus account PDA.
pub const BUS: &[u8] = b"bus";
/// The seed of the mint account PDA.
pub const MINT: &[u8] = b"mint";
/// The seed of the proof account PDA.
pub const PROOF: &[u8] = b"proof";
/// The seed of the treasury account PDA.
pub const TREASURY: &[u8] = b"treasury";
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Bus {
/// The bump of the bus account PDA.
pub bump: u32,
/// The ID of the bus account.
pub id: u32,
/// The quantity of rewards this bus can issue in the current epoch epoch.
pub available_rewards: u64,
}
impl Bus {
pub fn to_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Treasury {
/// The bump of the treasury account PDA.
pub bump: u64,
/// The admin authority with permission to update the difficulty.
pub admin: Pubkey,
/// The hash difficulty.
// pub difficulty: Hash,
/// The timestamp of the start of the current epoch.
pub epoch_start_at: i64,
/// The reward rate to payout to miners for submiting valid hashes.
pub reward_rate: u64,
/// The total lifetime claimed rewards.
pub total_claimed_rewards: u64,
}
impl Treasury {
pub fn to_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
}
#[cfg(test)]
mod tests {
use solana_program::{
keccak::{hashv, Hash},
pubkey::Pubkey,
};
use crate::{calculate_new_reward_rate, validate_hash, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS};
#[test]
fn test_validate_hash_pass() {
let h1 = Hash::new_from_array([1; 32]);
let signer = Pubkey::new_unique();
let nonce = 10u64;
let difficulty = Hash::new_from_array([255; 32]);
let h2 = hashv(&[
h1.to_bytes().as_slice(),
signer.to_bytes().as_slice(),
nonce.to_be_bytes().as_slice(),
]);
let res = validate_hash(h1, h2, signer, nonce, difficulty);
assert!(res.is_ok());
}
#[test]
fn test_validate_hash_fail() {
let h1 = Hash::new_from_array([1; 32]);
let signer = Pubkey::new_unique();
let nonce = 10u64;
let difficulty = Hash::new_from_array([255; 32]);
let h2 = Hash::new_from_array([2; 32]);
let res = validate_hash(h1, h2, signer, nonce, difficulty);
assert!(res.is_err());
}
#[test]
fn test_validate_hash_fail_difficulty() {
let h1 = Hash::new_from_array([1; 32]);
let signer = Pubkey::new_unique();
let nonce = 10u64;
let difficulty = Hash::new_from_array([0; 32]);
let h2 = hashv(&[
h1.to_bytes().as_slice(),
signer.to_bytes().as_slice(),
nonce.to_be_bytes().as_slice(),
]);
let res = validate_hash(h1, h2, signer, nonce, difficulty);
assert!(res.is_err());
}
#[test]
fn test_calculate_new_reward_rate_stable() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS);
assert!(new_rate.eq(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_no_chage() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, 0);
assert!(new_rate.eq(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_lower() {
let current_rate = 1000;
let new_rate =
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_add(1_000_000));
assert!(new_rate.lt(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_higher() {
let current_rate = 1000;
let new_rate =
calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS.saturating_sub(1_000_000));
assert!(new_rate.gt(&current_rate));
}
#[test]
fn test_calculate_new_reward_rate_max_smooth() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, 1);
assert!(new_rate.eq(&current_rate.saturating_mul(SMOOTHING_FACTOR)));
}
#[test]
fn test_calculate_new_reward_rate_min_smooth() {
let current_rate = 1000;
let new_rate = calculate_new_reward_rate(current_rate, u64::MAX);
assert!(new_rate.eq(&current_rate.saturating_div(SMOOTHING_FACTOR)));
}
}

65
src/loaders.rs Normal file
View File

@@ -0,0 +1,65 @@
use solana_program::{
account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, system_program,
};
pub fn load_signer<'a, 'info>(
info: &'a AccountInfo<'info>,
) -> Result<&'a AccountInfo<'info>, ProgramError> {
if !info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
Ok(info)
}
pub fn load_pda<'a, 'info>(
info: &'a AccountInfo<'info>,
seeds: &[&[u8]],
writable: bool,
) -> Result<&'a AccountInfo<'info>, ProgramError> {
let key = Pubkey::create_program_address(seeds, &crate::id())?;
if !info.key.eq(&key) {
return Err(ProgramError::InvalidSeeds);
}
if !info.owner.eq(&crate::id()) {
return Err(ProgramError::InvalidAccountOwner);
}
if writable {
if !info.is_writable {
return Err(ProgramError::InvalidAccountData);
}
}
Ok(info)
}
pub fn load_uninitialized_pda<'a, 'info>(
info: &'a AccountInfo<'info>,
seeds: &[&[u8]],
) -> Result<&'a AccountInfo<'info>, ProgramError> {
let key = Pubkey::create_program_address(seeds, &crate::id())?;
if !info.key.eq(&key) {
return Err(ProgramError::InvalidSeeds);
}
load_uninitialized_account(info)
}
pub fn load_uninitialized_account<'a, 'info>(
info: &'a AccountInfo<'info>,
) -> Result<&'a AccountInfo<'info>, ProgramError> {
if !info.is_writable {
return Err(ProgramError::InvalidAccountData);
}
if !info.data_is_empty() || !info.owner.eq(&system_program::id()) {
return Err(ProgramError::AccountAlreadyInitialized);
}
Ok(info)
}
pub fn load_account<'a, 'info>(
info: &'a AccountInfo<'info>,
key: Pubkey,
) -> Result<&'a AccountInfo<'info>, ProgramError> {
if !info.key.eq(&key) {
return Err(ProgramError::InvalidAccountData);
}
Ok(info)
}

121
tests/test_initialize.rs Normal file
View File

@@ -0,0 +1,121 @@
use ore::{
instruction::{InitializeArgs, OreInstruction},
Bus, Treasury, BUS, BUS_COUNT, INITIAL_REWARD_RATE, MINT, TREASURY,
};
use solana_program::{
hash::Hash,
instruction::{AccountMeta, Instruction},
program_option::COption,
program_pack::Pack,
pubkey::Pubkey,
system_program, sysvar,
};
use solana_program_test::{processor, BanksClient, ProgramTest};
use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
};
use spl_token::state::Mint;
#[tokio::test]
async fn test_initialize() {
// Setup
let (mut banks, payer, hash) = setup_program_test_env().await;
// Pdas
let bus_pdas = vec![
Pubkey::find_program_address(&[BUS, &[0]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[1]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[2]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[3]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[4]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[5]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[6]], &ore::id()),
Pubkey::find_program_address(&[BUS, &[7]], &ore::id()),
];
let mint_pda = Pubkey::find_program_address(&[MINT], &ore::id());
let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id());
let treasury_tokens_address =
spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0);
// Build ix
let ix = Instruction {
program_id: ore::ID,
accounts: vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(bus_pdas[0].0, false),
AccountMeta::new(bus_pdas[1].0, false),
AccountMeta::new(bus_pdas[2].0, false),
AccountMeta::new(bus_pdas[3].0, false),
AccountMeta::new(bus_pdas[4].0, false),
AccountMeta::new(bus_pdas[5].0, false),
AccountMeta::new(bus_pdas[6].0, false),
AccountMeta::new(bus_pdas[7].0, false),
AccountMeta::new(mint_pda.0, false),
AccountMeta::new(treasury_pda.0, false),
AccountMeta::new(treasury_tokens_address, false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
],
data: [
OreInstruction::Initialize.to_vec(),
InitializeArgs {
bus_0_bump: bus_pdas[0].1,
bus_1_bump: bus_pdas[1].1,
bus_2_bump: bus_pdas[2].1,
bus_3_bump: bus_pdas[3].1,
bus_4_bump: bus_pdas[4].1,
bus_5_bump: bus_pdas[5].1,
bus_6_bump: bus_pdas[6].1,
bus_7_bump: bus_pdas[7].1,
mint_bump: mint_pda.1,
treasury_bump: treasury_pda.1,
}
.to_bytes()
.to_vec(),
]
.concat(),
};
// Submit tx
let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], hash);
let res = banks.process_transaction(tx).await;
assert!(res.is_ok());
// Test bus state
for i in 0..BUS_COUNT {
let bus_account = banks.get_account(bus_pdas[i].0).await.unwrap().unwrap();
assert_eq!(bus_account.owner, ore::id());
let bus = bytemuck::try_from_bytes::<Bus>(&bus_account.data).unwrap();
assert_eq!(bus.bump as u8, bus_pdas[i].1);
assert_eq!(bus.id as u8, i as u8);
assert_eq!(bus.available_rewards, 0);
}
// Test treasury state
let treasury_account = banks.get_account(treasury_pda.0).await.unwrap().unwrap();
assert_eq!(treasury_account.owner, ore::id());
let treasury = bytemuck::try_from_bytes::<Treasury>(&treasury_account.data).unwrap();
assert_eq!(treasury.bump as u8, treasury_pda.1);
assert_eq!(treasury.admin, payer.pubkey());
assert_eq!(treasury.epoch_start_at as u8, 0);
assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE);
assert_eq!(treasury.total_claimed_rewards as u8, 0);
// Test mint state
let mint_account = banks.get_account(mint_pda.0).await.unwrap().unwrap();
assert_eq!(mint_account.owner, spl_token::id());
let mint = Mint::unpack(&mint_account.data).unwrap();
assert_eq!(mint.mint_authority, COption::Some(treasury_pda.0));
assert_eq!(mint.supply, 0);
assert_eq!(mint.decimals, ore::TOKEN_DECIMALS);
assert_eq!(mint.is_initialized, true);
assert_eq!(mint.freeze_authority, COption::None);
}
async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) {
let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction));
program_test.prefer_bpf(true);
program_test.start().await
}