1use anchor_lang::prelude::*;
2use anchor_spl::token::{self, MintTo, SetAuthority, Transfer};
3use spl_token::instruction::AuthorityType;
4
5declare_id!("hapiAwBQLYRXrjGn6FLCgC8FpQd2yWbKMqS6AYZ48g6");
6
7pub mod checker;
8pub mod context;
9pub mod error;
10pub mod state;
11
12use context::*;
13use error::{print_error, ErrorCode};
14pub use state::{
15 address::Address,
16 address::Category,
17 case::CaseStatus,
18 network::NetworkSchema,
19 reporter::{ReporterRole, ReporterStatus},
20};
21
22#[program]
23pub mod hapi_core {
24 use super::*;
25
26 pub fn initialize_community(
27 ctx: Context<InitializeCommunity>,
28 stake_unlock_epochs: u64,
29 confirmation_threshold: u8,
30 validator_stake: u64,
31 tracer_stake: u64,
32 full_stake: u64,
33 authority_stake: u64,
34 appraiser_stake: u64,
35 signer_bump: u8,
36 ) -> Result<()> {
37 let community = &mut ctx.accounts.community;
38
39 community.authority = *ctx.accounts.authority.key;
40 community.cases = 0;
41 community.stake_unlock_epochs = stake_unlock_epochs;
42 community.confirmation_threshold = confirmation_threshold;
43 community.stake_mint = ctx.accounts.stake_mint.to_account_info().key();
44 community.token_signer = ctx.accounts.token_signer.key();
45 community.token_signer_bump = signer_bump;
46 community.token_account = ctx.accounts.token_account.key();
47 community.treasury_token_account = ctx.accounts.treasury_token_account.key();
48 community.validator_stake = validator_stake;
49 community.tracer_stake = tracer_stake;
50 community.full_stake = full_stake;
51 community.authority_stake = authority_stake;
52 community.appraiser_stake = appraiser_stake;
53
54 Ok(())
55 }
56
57 pub fn update_community(
58 ctx: Context<UpdateCommunity>,
59 stake_unlock_epochs: u64,
60 confirmation_threshold: u8,
61 validator_stake: u64,
62 tracer_stake: u64,
63 full_stake: u64,
64 authority_stake: u64,
65 appraiser_stake: u64,
66 ) -> Result<()> {
67 let community = &mut ctx.accounts.community;
68
69 community.stake_unlock_epochs = stake_unlock_epochs;
70 community.confirmation_threshold = confirmation_threshold;
71 community.validator_stake = validator_stake;
72 community.tracer_stake = tracer_stake;
73 community.full_stake = full_stake;
74 community.authority_stake = authority_stake;
75 community.appraiser_stake = appraiser_stake;
76
77 Ok(())
78 }
79
80 pub fn set_community_authority(ctx: Context<SetCommunityAuthority>) -> Result<()> {
81 let community = &mut ctx.accounts.community;
82
83 community.authority = *ctx.accounts.new_authority.key;
84
85 Ok(())
86 }
87
88 pub fn create_network(
89 ctx: Context<CreateNetwork>,
90 name: [u8; 32],
91 schema: NetworkSchema,
92 address_tracer_reward: u64,
93 address_confirmation_reward: u64,
94 asset_tracer_reward: u64,
95 asset_confirmation_reward: u64,
96 network_bump: u8,
97 reward_signer_bump: u8,
98 report_price: u64,
99 ) -> Result<()> {
100 token::set_authority(
102 CpiContext::new(
103 ctx.accounts.token_program.to_account_info(),
104 SetAuthority {
105 current_authority: ctx.accounts.authority.to_account_info(),
106 account_or_mint: ctx.accounts.reward_mint.to_account_info(),
107 },
108 ),
109 AuthorityType::MintTokens,
110 Some(ctx.accounts.reward_signer.key()),
111 )?;
112
113 let network = &mut ctx.accounts.network;
114
115 network.community = ctx.accounts.community.key();
116 network.bump = network_bump;
117
118 network.name = name;
119 network.schema = schema;
120 network.reward_mint = ctx.accounts.reward_mint.key();
121 network.reward_signer = ctx.accounts.reward_signer.key();
122 network.reward_signer_bump = reward_signer_bump;
123 network.address_tracer_reward = address_tracer_reward;
124 network.address_confirmation_reward = address_confirmation_reward;
125 network.asset_tracer_reward = asset_tracer_reward;
126 network.asset_confirmation_reward = asset_confirmation_reward;
127 network.replication_price = report_price;
128
129 Ok(())
130 }
131
132 pub fn update_network(
133 ctx: Context<UpdateNetwork>,
134 address_tracer_reward: u64,
135 address_confirmation_reward: u64,
136 asset_tracer_reward: u64,
137 asset_confirmation_reward: u64,
138 ) -> Result<()> {
139 let network = &mut ctx.accounts.network;
140
141 network.address_tracer_reward = address_tracer_reward;
142 network.address_confirmation_reward = address_confirmation_reward;
143 network.asset_tracer_reward = asset_tracer_reward;
144 network.asset_confirmation_reward = asset_confirmation_reward;
145
146 Ok(())
147 }
148
149 pub fn create_reporter(
150 ctx: Context<CreateReporter>,
151 role: ReporterRole,
152 name: [u8; 32],
153 bump: u8,
154 ) -> Result<()> {
155 let reporter = &mut ctx.accounts.reporter;
156
157 reporter.community = ctx.accounts.community.key();
158 reporter.pubkey = *ctx.accounts.pubkey.key;
159 reporter.bump = bump;
160
161 reporter.role = role;
162 reporter.status = ReporterStatus::Inactive;
163 reporter.name = name;
164 reporter.is_frozen = false;
165 reporter.stake = 0;
166
167 Ok(())
168 }
169
170 pub fn update_reporter(
171 ctx: Context<UpdateReporter>,
172 role: ReporterRole,
173 name: [u8; 32],
174 ) -> Result<()> {
175 let reporter = &mut ctx.accounts.reporter;
176
177 reporter.role = role;
178 reporter.name = name;
179
180 Ok(())
181 }
182
183 pub fn create_case(
184 ctx: Context<CreateCase>,
185 case_id: u64,
186 name: [u8; 32],
187 bump: u8,
188 ) -> Result<()> {
189 let community = &mut ctx.accounts.community;
190
191 if case_id != community.cases + 1 {
192 return print_error(ErrorCode::NonSequentialCaseId);
193 } else {
194 community.cases = case_id;
195 }
196
197 let case = &mut ctx.accounts.case;
198
199 case.community = ctx.accounts.community.key();
200 case.id = case_id;
201 case.bump = bump;
202
203 case.name = name;
204 case.status = CaseStatus::Open;
205 case.reporter = ctx.accounts.reporter.key();
206
207 Ok(())
208 }
209
210 pub fn update_case(ctx: Context<UpdateCase>, name: [u8; 32], status: CaseStatus) -> Result<()> {
211 let case = &mut ctx.accounts.case;
212
213 case.name = name;
214 case.status = status;
215
216 Ok(())
217 }
218
219 pub fn create_address(
220 ctx: Context<CreateAddress>,
221 addr: [u8; 64],
222 category: Category,
223 risk: u8,
224 bump: u8,
225 ) -> Result<()> {
226 if risk > 10 {
227 return print_error(ErrorCode::RiskOutOfRange);
228 }
229
230 token::transfer(
231 CpiContext::new(
232 ctx.accounts.token_program.to_account_info(),
233 Transfer {
234 from: ctx
235 .accounts
236 .reporter_payment_token_account
237 .to_account_info(),
238 to: ctx.accounts.treasury_token_account.to_account_info(),
239 authority: ctx.accounts.sender.to_account_info(),
240 },
241 ),
242 ctx.accounts.network.replication_price,
243 )?;
244
245 let address = &mut ctx.accounts.address;
246
247 address.network = ctx.accounts.network.key();
248 address.address = addr;
249 address.bump = bump;
250
251 address.community = ctx.accounts.community.key();
252 address.reporter = ctx.accounts.reporter.key();
253 address.case_id = ctx.accounts.case.id;
254 address.category = category;
255 address.risk = risk;
256 address.confirmations = 0;
257 address.replication_bounty = ctx.accounts.network.replication_price;
258
259 Ok(())
260 }
261
262 pub fn confirm_address(ctx: Context<ConfirmAddress>) -> Result<()> {
263 let address = &mut ctx.accounts.address;
264
265 address.confirmations += 1;
266
267 let community = &ctx.accounts.community;
268
269 if address.confirmations == community.confirmation_threshold {
270 let address_reporter_reward = &mut ctx.accounts.address_reporter_reward.load_mut()?;
271
272 address_reporter_reward.address_tracer_counter += 1;
273 }
274
275 let reporter_reward = &mut ctx.accounts.reporter_reward.load_mut()?;
276
277 reporter_reward.address_confirmation_counter += 1;
278
279 Ok(())
280 }
281
282 pub fn update_address(ctx: Context<UpdateAddress>, category: Category, risk: u8) -> Result<()> {
283 if risk > 10 {
284 return print_error(ErrorCode::RiskOutOfRange);
285 }
286
287 token::transfer(
288 CpiContext::new(
289 ctx.accounts.token_program.to_account_info(),
290 Transfer {
291 from: ctx
292 .accounts
293 .reporter_payment_token_account
294 .to_account_info(),
295 to: ctx.accounts.treasury_token_account.to_account_info(),
296 authority: ctx.accounts.sender.to_account_info(),
297 },
298 ),
299 ctx.accounts.network.replication_price,
300 )?;
301
302 let address = &mut ctx.accounts.address;
303
304 address.risk = risk;
305 address.category = category;
306 address.replication_bounty = address
307 .replication_bounty
308 .checked_add(ctx.accounts.network.replication_price)
309 .unwrap();
310
311 Ok(())
312 }
313
314 pub fn change_address_case(ctx: Context<ChangeAddressCase>) -> Result<()> {
315 let address = &mut ctx.accounts.address;
316
317 address.case_id = ctx.accounts.new_case.id;
318
319 Ok(())
320 }
321
322 pub fn create_asset(
323 ctx: Context<CreateAsset>,
324 mint: [u8; 64],
325 asset_id: [u8; 32],
326 category: Category,
327 risk: u8,
328 bump: u8,
329 ) -> Result<()> {
330 if risk > 10 {
331 return print_error(ErrorCode::RiskOutOfRange);
332 }
333
334 token::transfer(
335 CpiContext::new(
336 ctx.accounts.token_program.to_account_info(),
337 Transfer {
338 from: ctx
339 .accounts
340 .reporter_payment_token_account
341 .to_account_info(),
342 to: ctx.accounts.treasury_token_account.to_account_info(),
343 authority: ctx.accounts.sender.to_account_info(),
344 },
345 ),
346 ctx.accounts.network.replication_price,
347 )?;
348
349 let asset = &mut ctx.accounts.asset;
350
351 asset.network = ctx.accounts.network.key();
352 asset.mint = mint;
353 asset.asset_id = asset_id;
354 asset.bump = bump;
355
356 asset.community = ctx.accounts.community.key();
357 asset.reporter = ctx.accounts.reporter.key();
358 asset.case_id = ctx.accounts.case.id;
359 asset.category = category;
360 asset.risk = risk;
361 asset.confirmations = 0;
362 asset.replication_bounty = ctx.accounts.network.replication_price;
363
364 Ok(())
365 }
366
367 pub fn confirm_asset(ctx: Context<ConfirmAsset>) -> Result<()> {
368 let asset = &mut ctx.accounts.asset;
369
370 asset.confirmations += 1;
371
372 let community = &ctx.accounts.community;
373
374 if asset.confirmations == community.confirmation_threshold {
375 let asset_reporter_reward = &mut ctx.accounts.asset_reporter_reward.load_mut()?;
376
377 asset_reporter_reward.asset_tracer_counter += 1;
378 }
379
380 let reporter_reward = &mut ctx.accounts.reporter_reward.load_mut()?;
381
382 reporter_reward.asset_confirmation_counter += 1;
383
384 Ok(())
385 }
386
387 pub fn update_asset(ctx: Context<UpdateAsset>, category: Category, risk: u8) -> Result<()> {
388 if risk > 10 {
389 return print_error(ErrorCode::RiskOutOfRange);
390 }
391
392 token::transfer(
393 CpiContext::new(
394 ctx.accounts.token_program.to_account_info(),
395 Transfer {
396 from: ctx
397 .accounts
398 .reporter_payment_token_account
399 .to_account_info(),
400 to: ctx.accounts.treasury_token_account.to_account_info(),
401 authority: ctx.accounts.sender.to_account_info(),
402 },
403 ),
404 ctx.accounts.network.replication_price,
405 )?;
406
407 let asset = &mut ctx.accounts.asset;
408
409 asset.risk = risk;
410 asset.category = category;
411 asset.replication_bounty = asset
412 .replication_bounty
413 .checked_add(ctx.accounts.network.replication_price)
414 .unwrap();
415
416 Ok(())
417 }
418
419 pub fn initialize_reporter_reward(
420 ctx: Context<InitializeReporterReward>,
421 bump: u8,
422 ) -> Result<()> {
423 let reporter_reward = &mut ctx.accounts.reporter_reward.load_init()?;
424
425 reporter_reward.network = ctx.accounts.network.key();
426 reporter_reward.reporter = ctx.accounts.reporter.key();
427 reporter_reward.bump = bump;
428
429 Ok(())
430 }
431
432 pub fn activate_reporter(ctx: Context<ActivateReporter>) -> Result<()> {
433 let community = &ctx.accounts.community;
434
435 let reporter = &mut ctx.accounts.reporter;
436
437 let stake = match reporter.role {
438 ReporterRole::Validator => community.validator_stake,
439 ReporterRole::Tracer => community.tracer_stake,
440 ReporterRole::Publisher => community.full_stake,
441 ReporterRole::Authority => community.authority_stake,
442 ReporterRole::Appraiser => community.appraiser_stake,
443 };
444
445 token::transfer(
446 CpiContext::new(
447 ctx.accounts.token_program.to_account_info(),
448 Transfer {
449 from: ctx.accounts.reporter_token_account.to_account_info(),
450 to: ctx.accounts.community_token_account.to_account_info(),
451 authority: ctx.accounts.sender.to_account_info(),
452 },
453 ),
454 stake,
455 )?;
456
457 reporter.status = ReporterStatus::Active;
458 reporter.stake = stake;
459
460 Ok(())
461 }
462
463 pub fn deactivate_reporter(ctx: Context<DeactivateReporter>) -> Result<()> {
464 let community = &ctx.accounts.community;
465
466 let reporter = &mut ctx.accounts.reporter;
467
468 reporter.status = ReporterStatus::Unstaking;
469 reporter.unlock_epoch = Clock::get()?.epoch + community.stake_unlock_epochs;
470
471 Ok(())
472 }
473
474 pub fn release_reporter(ctx: Context<ReleaseReporter>) -> Result<()> {
475 let reporter = &mut ctx.accounts.reporter;
476
477 if reporter.unlock_epoch > Clock::get()?.epoch {
478 return print_error(ErrorCode::ReleaseEpochInFuture);
479 }
480
481 let community = ctx.accounts.community.clone();
482
483 let token_signer = ctx.accounts.community_token_signer.clone();
484
485 let seeds = &[
486 b"community_stash".as_ref(),
487 community.to_account_info().key.as_ref(),
488 &[community.token_signer_bump],
489 ];
490 let signer = &[&seeds[..]];
491
492 let cpi_context = CpiContext::new_with_signer(
493 ctx.accounts.token_program.to_account_info(),
494 Transfer {
495 from: ctx.accounts.community_token_account.to_account_info(),
496 to: ctx.accounts.reporter_token_account.to_account_info(),
497 authority: token_signer.to_account_info(),
498 },
499 signer,
500 );
501
502 token::transfer(cpi_context, reporter.stake)?;
503
504 reporter.status = ReporterStatus::Inactive;
505 reporter.unlock_epoch = 0;
506 reporter.stake = 0;
507
508 Ok(())
509 }
510
511 pub fn claim_reporter_reward(ctx: Context<ClaimReporterReward>) -> Result<()> {
512 let network = &ctx.accounts.network;
513
514 let reporter_reward = &mut ctx.accounts.reporter_reward.load_mut()?;
515
516 let reward = network.address_confirmation_reward
517 * reporter_reward.address_confirmation_counter as u64
518 + network.address_tracer_reward * reporter_reward.address_tracer_counter as u64;
519
520 if reward == 0 {
521 return print_error(ErrorCode::NoReward);
522 }
523
524 reporter_reward.address_confirmation_counter = 0;
525 reporter_reward.address_tracer_counter = 0;
526
527 let reward_signer = &ctx.accounts.reward_signer;
528
529 let seeds = &[
530 b"network_reward",
531 network.to_account_info().key.as_ref(),
532 &[network.reward_signer_bump],
533 ];
534
535 token::mint_to(
536 CpiContext::new_with_signer(
537 ctx.accounts.token_program.to_account_info(),
538 MintTo {
539 mint: ctx.accounts.reward_mint.to_account_info(),
540 to: ctx.accounts.reporter_token_account.to_account_info(),
541 authority: reward_signer.to_account_info(),
542 },
543 &[&seeds[..]],
544 ),
545 reward,
546 )?;
547
548 Ok(())
549 }
550
551 pub fn freeze_reporter(ctx: Context<FreezeReporter>) -> Result<()> {
552 let reporter = &mut ctx.accounts.reporter;
553
554 reporter.is_frozen = true;
555
556 Ok(())
557 }
558
559 pub fn unfreeze_reporter(ctx: Context<UnfreezeReporter>) -> Result<()> {
560 let reporter = &mut ctx.accounts.reporter;
561
562 reporter.is_frozen = false;
563
564 Ok(())
565 }
566
567 pub fn update_replication_price(
568 ctx: Context<UpdateReplicationPrice>,
569 price: u64,
570 ) -> Result<()> {
571 let network = &mut ctx.accounts.network;
572
573 network.replication_price = price;
574
575 Ok(())
576 }
577}