hapi_core/
lib.rs

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        // Pass authority to network signer PDA
101        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}