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