1pub mod calculations;
2pub mod consts;
3pub mod dmt;
4pub mod error;
5pub mod event;
6pub mod instruction;
7pub mod loaders;
8pub mod sdk;
9pub mod state;
10pub mod utils;
11
12pub use consts::{calculate_daily_rewards, BASE_DAILY_REWARDS};
14
15pub use calculations::{
16 calculate_community_score, calculate_smooth_community_decay, calculate_time_decay,
17 calculate_weighted_geometric_mean,
18};
19
20pub mod prelude {
21 pub use crate::dmt::*;
23 pub use crate::error::*;
24 pub use crate::event::*;
25 pub use crate::instruction::*;
26 pub use crate::loaders::*;
27 pub use crate::state::*;
28
29 pub use crate::sdk::*;
31
32 pub use crate::sdk::{
34 create_participant_from_wallet, find_participant_by_wallet, find_participant_index,
35 generate_proof_for_wallet, sort_participants_by_id, wallet_to_participant_id, MerkleProof,
36 MerkleTree,
37 };
38
39 pub use crate::consts::{
41 calculate_daily_rewards, calculate_daily_rewards_split, BASE_DAILY_REWARDS, BASIS_POINTS,
42 EPOCH_DURATION, MAX_PAYMENT_PROOF_LENGTH, MAX_SEAL_PROOF_LENGTH, MAX_SUPPLY, ONE_MIRACLE,
43 TEN_MIRACLE,
44 };
45
46 pub use crate::calculations::{
48 calculate_community_score, calculate_smooth_community_decay, calculate_time_decay,
49 calculate_weighted_geometric_mean,
50 };
51
52 pub use crate::utils::*;
54}
55
56use steel::*;
57
58declare_id!("1stNeamE9aWAxA1bvN6h659hYZP6BKPZuLirNUwPcPD");
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::calculations::{
71 calculate_community_score, calculate_smooth_community_decay, calculate_time_decay,
72 calculate_weighted_geometric_mean,
73 };
74 use crate::consts::{calculate_daily_rewards, BASE_DAILY_REWARDS};
75 use crate::state::{ActivityLimits, CommunityTargets, Config, SocialMarketingConfig};
76
77 fn create_test_config() -> Config {
79 Config {
80 last_reset_at: 0,
81 claim_rewards_threshold: BASE_DAILY_REWARDS,
84 oracle_authority: solana_program::pubkey::Pubkey::default(),
85 community_targets: CommunityTargets {
86 target_weekly_users: 10000,
87 target_weekly_activity: 50000,
88 target_retention_rate: 7000,
89 _padding: [0u8; 6],
90 },
91 activity_limits: ActivityLimits {
92 max_customer_activity_per_epoch: 5,
93 max_merchant_activity_per_epoch: 50,
94 activity_cap_enabled: 1,
95 claim_cap_enabled: 1,
96 _padding: [0u8; 6],
97 },
98 social_marketing: SocialMarketingConfig {
99 rewards_pool: 9_000_000_000_000, daily_limit_per_user: 1, base_reward_per_post: 10_000_000, oracle_authority: solana_program::pubkey::Pubkey::default(),
103 },
105 }
107 }
108
109 #[test]
110 fn test_id() {
111 assert_eq!(id(), ID);
112 }
113
114 #[test]
115 fn test_community_metrics_size() {
116 use state::Metrics;
117 assert_eq!(std::mem::size_of::<Metrics>(), 32);
118 }
119
120 #[test]
121 fn test_community_metrics_event_size() {
122 use event::MetricsUpdatedEvent;
123 assert_eq!(std::mem::size_of::<MetricsUpdatedEvent>(), 32);
124 }
125
126 #[test]
129 fn test_smooth_community_decay() {
130 assert_eq!(calculate_smooth_community_decay(0), 10000); assert_eq!(calculate_smooth_community_decay(10000), 5000); let mid_decay = calculate_smooth_community_decay(5000);
136 assert!(mid_decay >= 5000 && mid_decay <= 10000); let decay_7999 = calculate_smooth_community_decay(7999);
140 let decay_8000 = calculate_smooth_community_decay(8000);
141 let decay_8001 = calculate_smooth_community_decay(8001);
142
143 assert!(decay_7999 >= decay_8000);
145 assert!(decay_8000 >= decay_8001);
146 assert!(decay_7999 - decay_8000 <= 10); assert!(decay_8000 - decay_8001 <= 10); }
149
150 #[test]
151 fn test_daily_rewards() {
152 let base_rewards = BASE_DAILY_REWARDS; let expected_0 = (base_rewards as u128)
156 .saturating_mul(calculate_smooth_community_decay(0) as u128)
157 .saturating_mul(calculate_time_decay(0) as u128)
158 .saturating_div(100_000_000u128) as u64;
159 assert_eq!(calculate_daily_rewards(0, 0), expected_0); let expected_100 = (base_rewards as u128)
162 .saturating_mul(calculate_smooth_community_decay(10000) as u128)
163 .saturating_mul(calculate_time_decay(0) as u128)
164 .saturating_div(100_000_000u128) as u64;
165 assert_eq!(calculate_daily_rewards(10000, 0), expected_100); let mid_rewards = calculate_daily_rewards(5000, 0);
169 assert!(mid_rewards > expected_100); assert!(mid_rewards <= base_rewards); let rewards_7999 = calculate_daily_rewards(7999, 0);
174 let rewards_8000 = calculate_daily_rewards(8000, 0);
175
176 assert!(rewards_7999 >= rewards_8000); }
179
180 #[test]
181 fn test_smooth_decay_formula_verification() {
182 assert_eq!(calculate_smooth_community_decay(0), 10000);
187
188 assert_eq!(calculate_smooth_community_decay(10000), 5000);
190
191 let expected_5000 = (0.875 * 10000.0) as u64;
193 let actual_5000 = calculate_smooth_community_decay(5000);
194 assert!(actual_5000 >= expected_5000 - 100 && actual_5000 <= expected_5000 + 100);
195
196 let score = 5000u64;
198 let score_squared = score * score; let score_ratio_squared_in_bps = score_squared / 10000; let quadratic_component = 10000 - score_ratio_squared_in_bps; let decay_component = quadratic_component * 5000 / 10000; let expected = 5000 + decay_component; let actual = calculate_smooth_community_decay(5000);
204 assert_eq!(actual, expected);
205
206 let score = 3000u64;
208 let score_squared = score * score; let score_ratio_squared_in_bps = score_squared / 10000; let quadratic_component = 10000 - score_ratio_squared_in_bps; let decay_component = quadratic_component * 5000 / 10000; let expected = 5000 + decay_component; let actual = calculate_smooth_community_decay(3000);
214 assert_eq!(actual, expected);
215 }
216
217 #[test]
218 fn test_weighted_geometric_mean() {
219 let scores = [5000u16, 7000u16, 9000u16]; let equal_weights = [3333u16, 3333u16, 3334u16]; let result = calculate_weighted_geometric_mean(scores, equal_weights);
224 assert!(result >= 6800 && result <= 6810);
226
227 let zero_weights = [0u16, 0u16, 0u16];
229 let result_zero = calculate_weighted_geometric_mean(scores, zero_weights);
230 assert_eq!(result_zero, 0);
231
232 let single_weight = [10000u16, 0u16, 0u16];
234 let result_single = calculate_weighted_geometric_mean(scores, single_weight);
235 assert_eq!(result_single, 5000);
236
237 let zero_scores = [0u16, 7000u16, 9000u16];
239 let result_zero_score = calculate_weighted_geometric_mean(zero_scores, equal_weights);
240 assert_eq!(result_zero_score, 0);
241
242 let mixed_scores = [1000u16, 5000u16, 9000u16]; let arithmetic_result = (1000 + 5000 + 9000) / 3; let geometric_result = calculate_weighted_geometric_mean(mixed_scores, equal_weights);
246 assert!(geometric_result < arithmetic_result);
248 }
249
250 #[test]
251 fn test_geometric_vs_arithmetic_mean_behavior() {
252 let balanced_scores = [6000u16, 7000u16, 8000u16];
254 let equal_weights = [3333u16, 3333u16, 3334u16];
255
256 let _arithmetic_mean = (6000 + 7000 + 8000) / 3; let geometric_mean = calculate_weighted_geometric_mean(balanced_scores, equal_weights);
258
259 assert!(geometric_mean >= 6900 && geometric_mean <= 7100);
261
262 let mixed_scores = [1000u16, 5000u16, 9000u16]; let arithmetic_mean_mixed = (1000 + 5000 + 9000) / 3; let geometric_mean_mixed = calculate_weighted_geometric_mean(mixed_scores, equal_weights);
266
267 assert!(geometric_mean_mixed < arithmetic_mean_mixed);
269 assert!(geometric_mean_mixed >= 2000 && geometric_mean_mixed <= 4000);
270
271 let poor_performance = [500u16, 8000u16, 9000u16]; let arithmetic_mean_poor = (500 + 8000 + 9000) / 3; let geometric_mean_poor =
275 calculate_weighted_geometric_mean(poor_performance, equal_weights);
276
277 assert!(geometric_mean_poor < arithmetic_mean_poor);
279 assert!(geometric_mean_poor < 4000); let zero_score = [0u16, 8000u16, 9000u16];
283 let geometric_mean_zero = calculate_weighted_geometric_mean(zero_score, equal_weights);
284 assert_eq!(geometric_mean_zero, 0);
285 }
286
287 #[test]
288 fn test_community_score_with_weights() {
289 let config = create_test_config();
290
291 let score = calculate_community_score(&config, 5000, 25000, 5000, 6000, 3000, 1000);
293 assert!(score > 0 && score <= 10000);
294
295 let score = calculate_community_score(&config, 5000, 25000, 5000, 4000, 3500, 2500);
297 assert!(score > 0 && score <= 10000);
298
299 let score = calculate_community_score(&config, 5000, 25000, 5000, 3000, 3000, 4000);
301 assert!(score > 0 && score <= 10000);
302 }
303
304 #[test]
305 fn test_time_decay() {
306 assert_eq!(calculate_time_decay(0), 10000);
308 assert_eq!(calculate_time_decay(1), 8200);
310 assert_eq!(calculate_time_decay(3), 4600);
312 assert_eq!(calculate_time_decay(5), 1000);
314 assert_eq!(calculate_time_decay(10), 1000);
316 }
317
318 #[test]
319 fn test_daily_rewards_with_time_decay() {
320 let base = BASE_DAILY_REWARDS;
321 let r1 = calculate_daily_rewards(10000, 0);
323 let expected1 = (base as u128)
324 .saturating_mul(calculate_smooth_community_decay(10000) as u128)
325 .saturating_mul(calculate_time_decay(0) as u128)
326 .saturating_div(100_000_000u128) as u64;
327 assert_eq!(r1, expected1);
328 let r2 = calculate_daily_rewards(10000, 5);
330 let expected2 = (base as u128)
331 .saturating_mul(calculate_smooth_community_decay(10000) as u128)
332 .saturating_mul(calculate_time_decay(5) as u128)
333 .saturating_div(100_000_000u128) as u64;
334 assert_eq!(r2, expected2);
335 let r3 = calculate_daily_rewards(5000, 3);
337 let expected3 = (base as u128)
338 .saturating_mul(calculate_smooth_community_decay(5000) as u128)
339 .saturating_mul(calculate_time_decay(3) as u128)
340 .saturating_div(100_000_000u128) as u64;
341 assert!((r3 as i64 - expected3 as i64).abs() < 10_000_000); }
343
344 #[test]
347 fn test_validate_community_metrics_success() {
348 let result = sdk::validate_community_metrics(
350 5000, 7500, 4000, 3500, 2500, 50_000, 7000, 3000, );
359 assert!(result.is_ok());
360 }
361
362 #[test]
363 fn test_validate_community_metrics_invalid_weights() {
364 let result = sdk::validate_community_metrics(
366 5000, 7500, 4000, 3500, 2000, 50_000, 7000, 3000, );
375 assert!(result.is_err());
376 assert_eq!(result.unwrap_err(), error::MiracleError::InvalidWeights);
377 }
378
379 #[test]
380 fn test_validate_community_metrics_invalid_retention_rate() {
381 let result = sdk::validate_community_metrics(
383 5000, 15000, 4000, 3500, 2500, 50_000, 7000, 3000, );
392 assert!(result.is_err());
393 assert_eq!(
394 result.unwrap_err(),
395 error::MiracleError::InvalidRetentionRate
396 );
397 }
398
399 #[test]
400 fn test_validate_community_metrics_invalid_reward_split() {
401 let result = sdk::validate_community_metrics(
403 5000, 7500, 4000, 3500, 2500, 50_000, 7000, 2000, );
412 assert!(result.is_err());
413 assert_eq!(result.unwrap_err(), error::MiracleError::InvalidRewardSplit);
414 }
415
416 #[test]
417 fn test_validate_community_metrics_invalid_activity_count() {
418 let result = sdk::validate_community_metrics(
420 5000, 7500, 4000, 3500, 2500, 2_000_000, 7000, 3000, );
429 assert!(result.is_err());
430 assert_eq!(
431 result.unwrap_err(),
432 error::MiracleError::InvalidActivityCount
433 );
434 }
435
436 #[test]
439 fn test_activity_based_rewards() {
440 let config = create_test_config();
441
442 let score_low_activity = calculate_community_score(
444 &config, 5000, 10_000, 5000, 4000, 3500, 2500, );
451
452 let score_high_activity = calculate_community_score(
453 &config, 5000, 100_000, 5000, 4000, 3500, 2500, );
460
461 assert!(score_high_activity > score_low_activity);
463 }
464
465 #[test]
466 fn test_customer_merchant_split() {
467 let (customer_pool, merchant_pool) = consts::calculate_daily_rewards_split(
469 5000, 1, 7000, 3000, );
474
475 assert!(customer_pool > 0);
477 assert!(merchant_pool > 0);
478
479 assert!(customer_pool > merchant_pool);
481
482 let total_rewards = calculate_daily_rewards(5000, 1);
484 let total_split = customer_pool + merchant_pool;
485 assert!((total_split as i64 - total_rewards as i64).abs() < 1000); }
487
488 #[test]
489 fn test_boundary_conditions() {
490 let config = create_test_config();
491
492 let _score_min = calculate_community_score(
494 &config, 1, 1, 0, 10000, 0, 0, );
501 let score_max = calculate_community_score(
505 &config, 1_000_000, 1_000_000, 10000, 3333, 3333, 3334, );
512 assert!(score_max <= 10000);
513
514 let _score_zero = calculate_community_score(
516 &config, 0, 0, 0, 3333, 3333, 3334, );
523 }
525
526 #[test]
527 fn test_weight_combinations() {
528 let test_cases = vec![
530 (6000, 3000, 1000), (4000, 3500, 2500), (3000, 3000, 4000), (5000, 5000, 0), (0, 0, 10000), ];
536
537 let config = create_test_config();
538 for (user_w, activity_w, retention_w) in test_cases {
539 let score = calculate_community_score(
540 &config,
541 5000, 50_000, 5000, user_w, activity_w, retention_w, );
548 assert!(score <= 10000); }
550 }
551}
552
553#[cfg(test)]
554mod date_epoch_tests {
555 use super::*;
556 use crate::consts::{EPOCH_DURATION, START_AT};
557
558 #[test]
559 fn test_timestamp_to_epoch() {
560 assert_eq!(sdk::timestamp_to_epoch(START_AT), 0);
562
563 assert_eq!(sdk::timestamp_to_epoch(START_AT - 1), 0);
565
566 assert_eq!(sdk::timestamp_to_epoch(START_AT + EPOCH_DURATION), 1);
568
569 assert_eq!(sdk::timestamp_to_epoch(START_AT + 2 * EPOCH_DURATION), 2);
571 }
572
573 #[test]
574 fn test_epoch_to_timestamp() {
575 let start_day_beginning = (START_AT / EPOCH_DURATION) * EPOCH_DURATION;
577
578 assert_eq!(sdk::epoch_to_timestamp(0), start_day_beginning);
580
581 assert_eq!(
583 sdk::epoch_to_timestamp(1),
584 start_day_beginning + EPOCH_DURATION
585 );
586
587 assert_eq!(
589 sdk::epoch_to_timestamp(2),
590 start_day_beginning + 2 * EPOCH_DURATION
591 );
592 }
593
594 #[test]
595 fn test_date_range_to_epochs() {
596 let epochs: Vec<u64> = sdk::date_range_to_epochs(START_AT, START_AT);
598 assert_eq!(epochs, vec![0u64]);
599
600 let epochs: Vec<u64> = sdk::date_range_to_epochs(START_AT, START_AT + EPOCH_DURATION);
602 assert_eq!(epochs, vec![0u64, 1u64]);
603
604 let epochs: Vec<u64> = sdk::date_range_to_epochs(START_AT, START_AT + 2 * EPOCH_DURATION);
606 assert_eq!(epochs, vec![0u64, 1u64, 2u64]);
607
608 let epochs: Vec<u64> = sdk::date_range_to_epochs(START_AT + EPOCH_DURATION, START_AT);
610 assert_eq!(epochs, vec![] as Vec<u64>);
611 }
612
613 #[test]
614 fn test_round_trip_conversion() {
615 let start_day_beginning = (START_AT / EPOCH_DURATION) * EPOCH_DURATION;
618 let original_timestamp = start_day_beginning + 5 * EPOCH_DURATION;
619 let epoch = sdk::timestamp_to_epoch(original_timestamp);
620 let converted_timestamp = sdk::epoch_to_timestamp(epoch);
621 assert_eq!(original_timestamp, converted_timestamp);
622
623 let original_epoch = 10;
625 let timestamp = sdk::epoch_to_timestamp(original_epoch);
626 let converted_epoch = sdk::timestamp_to_epoch(timestamp);
627 assert_eq!(original_epoch, converted_epoch);
628 }
629}
630
631#[cfg(test)]
632mod alignment_tests {
633 }