1use std::mem::size_of;
2
3use solana_program::{
4 account_info::AccountInfo,
5 clock::Clock,
6 entrypoint::ProgramResult,
7 keccak::{hashv, Hash as KeccakHash, HASH_BYTES},
8 program::set_return_data,
9 program_error::ProgramError,
10 program_memory::sol_memcmp,
11 pubkey::Pubkey,
12 slot_hashes::SlotHash,
13 sysvar::{self, Sysvar},
14};
15
16use crate::{
17 error::OreError,
18 instruction::MineArgs,
19 loaders::*,
20 state::{Bus, Proof, Treasury},
21 utils::AccountDeserialize,
22 EPOCH_DURATION, START_AT,
23};
24
25pub fn process_mine<'a, 'info>(
39 _program_id: &Pubkey,
40 accounts: &'a [AccountInfo<'info>],
41 data: &[u8],
42) -> ProgramResult {
43 let args = MineArgs::try_from_bytes(data)?;
45
46 let [signer, bus_info, proof_info, treasury_info, slot_hashes_info] = accounts else {
48 return Err(ProgramError::NotEnoughAccountKeys);
49 };
50 load_signer(signer)?;
51 load_any_bus(bus_info, true)?;
52 load_proof(proof_info, signer.key, true)?;
53 load_treasury(treasury_info, false)?;
54 load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?;
55
56 let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?;
58 if clock.unix_timestamp.lt(&START_AT) {
59 return Err(OreError::NotStarted.into());
60 }
61
62 let treasury_data = treasury_info.data.borrow();
64 let treasury = Treasury::try_from_bytes(&treasury_data)?;
65 let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION);
66 if clock.unix_timestamp.ge(&threshold) {
67 return Err(OreError::NeedsReset.into());
68 }
69
70 let mut proof_data = proof_info.data.borrow_mut();
72 let proof = Proof::try_from_bytes_mut(&mut proof_data)?;
73 validate_hash(
74 args.hash.into(),
75 proof.hash.into(),
76 *signer.key,
77 u64::from_le_bytes(args.nonce),
78 treasury.difficulty.into(),
79 )?;
80
81 let mut bus_data = bus_info.data.borrow_mut();
83 let bus = Bus::try_from_bytes_mut(&mut bus_data)?;
84 bus.rewards = bus
85 .rewards
86 .checked_sub(treasury.reward_rate)
87 .ok_or(OreError::BusRewardsInsufficient)?;
88 proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate);
89
90 proof.hash = hashv(&[
92 KeccakHash::from(args.hash).as_ref(),
93 &slot_hashes_info.data.borrow()[0..size_of::<SlotHash>()],
94 ])
95 .into();
96
97 proof.total_hashes = proof.total_hashes.saturating_add(1);
99 proof.total_rewards = proof.total_rewards.saturating_add(treasury.reward_rate);
100
101 set_return_data(treasury.reward_rate.to_le_bytes().as_slice());
103
104 Ok(())
105}
106
107pub(crate) fn validate_hash(
110 hash: KeccakHash,
111 current_hash: KeccakHash,
112 signer: Pubkey,
113 nonce: u64,
114 difficulty: KeccakHash,
115) -> Result<(), ProgramError> {
116 let hash_ = hashv(&[
118 current_hash.as_ref(),
119 signer.as_ref(),
120 nonce.to_le_bytes().as_slice(),
121 ]);
122 if sol_memcmp(hash.as_ref(), hash_.as_ref(), HASH_BYTES) != 0 {
123 return Err(OreError::HashInvalid.into());
124 }
125
126 if hash.gt(&difficulty) {
128 return Err(OreError::DifficultyNotSatisfied.into());
129 }
130
131 Ok(())
132}
133
134#[cfg(test)]
135mod tests {
136 use solana_program::{
137 keccak::{hashv, Hash, HASH_BYTES},
138 pubkey::Pubkey,
139 };
140
141 use crate::validate_hash;
142
143 #[test]
144 fn test_validate_hash_pass() {
145 let h1 = Hash::new_from_array([1; HASH_BYTES]);
146 let signer = Pubkey::new_unique();
147 let nonce = 10u64;
148 let difficulty = Hash::new_from_array([255; HASH_BYTES]);
149 let h2 = hashv(&[
150 h1.to_bytes().as_slice(),
151 signer.to_bytes().as_slice(),
152 nonce.to_le_bytes().as_slice(),
153 ]);
154 let res = validate_hash(h2, h1, signer, nonce, difficulty);
155 assert!(res.is_ok());
156 }
157
158 #[test]
159 fn test_validate_hash_fail() {
160 let h1 = Hash::new_from_array([1; HASH_BYTES]);
161 let signer = Pubkey::new_unique();
162 let nonce = 10u64;
163 let difficulty = Hash::new_from_array([255; HASH_BYTES]);
164 let h2 = Hash::new_from_array([2; HASH_BYTES]);
165 let res = validate_hash(h2, h1, signer, nonce, difficulty);
166 assert!(res.is_err());
167 }
168
169 #[test]
170 fn test_validate_hash_fail_difficulty() {
171 let h1 = Hash::new_from_array([1; HASH_BYTES]);
172 let signer = Pubkey::new_unique();
173 let nonce = 10u64;
174 let difficulty = Hash::new_from_array([0; HASH_BYTES]);
175 let h2 = hashv(&[
176 h1.to_bytes().as_slice(),
177 signer.to_bytes().as_slice(),
178 nonce.to_le_bytes().as_slice(),
179 ]);
180 let res = validate_hash(h2, h1, signer, nonce, difficulty);
181 assert!(res.is_err());
182 }
183}