1use cosmwasm_schema::cw_serde;
7use cosmwasm_std::{Addr, Binary, StdError, Timestamp, Uint128};
8use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey};
9
10#[cw_serde]
13pub enum DDAlgorithmType {
15 SingleTargetLottery,
17 MultiTargetLottery,
19 MultiLevelLottery,
21 MultiTargetVoting,
23 HierarchicalDistribution,
25 SingleTargetWithCompensation,
27 PokerShuffleOneTime,
29 PokerShuffleSequential,
31}
32
33impl DDAlgorithmType {
34 pub fn as_str(&self) -> &'static str {
36 match self {
37 DDAlgorithmType::SingleTargetLottery => "single_target_lottery",
38 DDAlgorithmType::MultiTargetLottery => "multi_target_lottery",
39 DDAlgorithmType::MultiLevelLottery => "multi_level_lottery",
40 DDAlgorithmType::MultiTargetVoting => "multi_target_voting",
41 DDAlgorithmType::HierarchicalDistribution => "hierarchical_distribution",
42 DDAlgorithmType::SingleTargetWithCompensation => "single_target_with_compensation",
43 DDAlgorithmType::PokerShuffleOneTime => "poker_shuffle_one_time",
44 DDAlgorithmType::PokerShuffleSequential => "poker_shuffle_sequential",
45 }
46 }
47
48 pub fn all() -> &'static [DDAlgorithmType] {
50 use DDAlgorithmType::*;
51 const VARIANTS: [DDAlgorithmType; 8] = [
52 SingleTargetLottery,
53 MultiTargetLottery,
54 MultiLevelLottery,
55 MultiTargetVoting,
56 HierarchicalDistribution,
57 SingleTargetWithCompensation,
58 PokerShuffleOneTime,
59 PokerShuffleSequential,
60 ];
61 &VARIANTS
62 }
63}
64
65impl<'a> PrimaryKey<'a> for DDAlgorithmType {
66 type Prefix = ();
67 type SubPrefix = ();
68 type Suffix = Self;
69 type SuperSuffix = Self;
70
71 fn key(&self) -> Vec<Key<'_>> {
73 vec![Key::Ref(self.as_str().as_bytes())]
74 }
75}
76
77impl KeyDeserialize for DDAlgorithmType {
78 type Output = Self;
79 const KEY_ELEMS: u16 = 1;
80
81 fn from_vec(value: Vec<u8>) -> Result<Self, StdError> {
83 let s = String::from_utf8(value)
84 .map_err(|_| StdError::generic_err("DDAlgorithmType: invalid utf8"))?;
85 match s.as_str() {
86 "single_target_lottery" => Ok(DDAlgorithmType::SingleTargetLottery),
87 "multi_target_lottery" => Ok(DDAlgorithmType::MultiTargetLottery),
88 "multi_level_lottery" => Ok(DDAlgorithmType::MultiLevelLottery),
89 "multi_target_voting" => Ok(DDAlgorithmType::MultiTargetVoting),
90 "hierarchical_distribution" => Ok(DDAlgorithmType::HierarchicalDistribution),
91 "single_target_with_compensation" => Ok(DDAlgorithmType::SingleTargetWithCompensation),
92 "poker_shuffle_one_time" => Ok(DDAlgorithmType::PokerShuffleOneTime),
93 "poker_shuffle_sequential" => Ok(DDAlgorithmType::PokerShuffleSequential),
94 _ => Err(StdError::generic_err("DDAlgorithmType: unknown variant")),
95 }
96 }
97}
98
99#[cw_serde]
102pub struct RewardAttribute {
104 pub trait_type: String,
106 pub value: String,
108 pub display_type: Option<String>,
110}
111
112#[cw_serde]
113pub struct VotingReward {
115 pub name: String,
117 pub description: String,
119 pub image_uri: Option<String>,
121 pub external_link: Option<String>,
123 pub attributes: Option<Vec<RewardAttribute>>,
125 pub background_color: Option<String>,
127 pub animation_url: Option<String>,
129 pub youtube_url: Option<String>,
131}
132
133#[cw_serde]
134pub struct RewardMetadata {
136 pub name: String,
138 pub description: String,
140 pub image_uri: Option<String>,
142 pub external_link: Option<String>,
144 pub attributes: Option<Vec<RewardAttribute>>,
146 pub background_color: Option<String>,
148 pub animation_url: Option<String>,
150 pub youtube_url: Option<String>,
152 pub minted_at: Timestamp,
154 pub lottery_session_id: String,
156 pub winner: String,
158}
159
160#[cw_serde]
161pub enum DistributionStrategy {
163 Random,
165 ByRank,
167 ByWeight,
169 Custom(String),
171}
172
173#[cw_serde]
174pub struct RewardDistribution {
176 pub winner: String,
178 pub token_id: String,
180}
181
182#[cw_serde]
185pub enum TimeControlMode {
187 BlockHeight {
189 commitment_height: u64,
191 reveal_height: u64,
193 },
194 TimeInterval {
196 commitment_duration: u64,
198 reveal_duration: u64,
200 },
201}
202
203#[cw_serde]
206pub enum LotteryStatus {
208 Created,
210 CommitmentPhase,
212 RevealPhase,
214 Completed,
216 Cancelled,
218}
219
220#[cw_serde]
221pub struct LotterySessionInfo {
223 pub session_id: String,
225 pub title: String,
227 pub description: String,
229 pub creator: String,
231 pub created_at: Timestamp,
233 pub total_rewards: u32,
235 pub minted_rewards: u32,
237}
238
239#[cw_serde]
240pub struct LotterySession {
242 pub session_id: String,
244 pub title: String,
246 pub description: String,
248 pub participants: Vec<String>,
250 pub nft_contract: Option<String>,
252 pub creator: String,
254 pub status: LotteryStatus,
256 pub created_at: Timestamp,
258 pub created_height: u64,
260 pub time_control: TimeControlMode,
262 pub commitment_deadline: Option<Timestamp>,
264 pub reveal_deadline: Option<Timestamp>,
266 pub commitment_deadline_height: Option<u64>,
268 pub reveal_deadline_height: Option<u64>,
270 pub reward: VotingReward,
272 pub result: Option<LotteryResult>,
274}
275
276#[cw_serde]
277pub struct LotteryResult {
279 pub session_id: String,
281 pub winner: String,
283 pub random_seed: u64,
285 pub random_number: u64,
287 pub result_hash: String,
289 pub total_participants: u32,
291 pub executed_at: Timestamp,
293 pub algorithm_type: String,
295}
296
297#[cw_serde]
300pub struct NftInstantiateMsg {
302 pub name: String,
304 pub symbol: String,
306 pub minter: String,
308 pub participants: Vec<String>,
310 pub max_participants: u32,
312 pub session_active: Option<bool>,
314}
315
316#[cw_serde]
317pub enum NftExecuteMsg {
319 SubmitCommitment {
322 commitment_hash: String,
324 salt: String,
326 },
327 SubmitReveal {
329 vote_data: Binary,
331 salt: String,
333 },
334
335 AddParticipant {
338 participant: String,
340 },
341 AddParticipants {
343 participants: Vec<String>,
345 },
346 RemoveParticipant {
348 participant: String,
350 },
351
352 SetTransfersLocked {
355 locked: bool,
357 },
358 SetSessionActive {
360 active: bool,
362 },
363
364 TransferNft {
367 recipient: String,
369 token_id: String,
371 },
372}
373
374#[cw_serde]
375pub enum NftQueryMsg {
377 GetParticipants {},
380 IsParticipant { participant: String },
382 GetParticipantCount {},
384 GetMaxParticipants {},
386 GetParticipantIndex { address: String },
388
389 GetCommitment { participant: String },
392 GetReveal { participant: String },
394 GetAllCommitments {},
396 GetAllReveals {},
398 HasCommitment { participant: String },
400 HasReveal { participant: String },
402
403 GetSessionActive {},
406 GetTransfersLocked {},
408 GetSessionMeta {},
410
411 OwnerOf { token_id: String },
414
415 GetCurrentTime {},
418 GetCurrentHeight {},
420
421 CanAddParticipants { count: u32 },
424 GetVotingStatus {},
426}
427
428#[cw_serde]
429pub struct NftSessionMeta {
431 pub participants: Vec<String>,
433 pub max_participants: u32,
435 pub session_active: bool,
437}
438
439#[cw_serde]
440pub struct CommitmentInfo {
442 pub participant: String,
444 pub commitment_hash: String,
446 pub salt: String,
448 pub submitted_at: u64,
450}
451
452#[cw_serde]
453pub struct RevealInfo {
455 pub participant: String,
457 pub vote_data: Binary,
459 pub salt: String,
461 pub submitted_at: u64,
463}
464
465#[cw_serde]
466pub struct VotingStatus {
468 pub total_participants: u32,
470 pub commitments_count: u32,
472 pub reveals_count: u32,
474 pub session_active: bool,
476 pub all_committed: bool,
478 pub all_revealed: bool,
480}
481
482#[cw_serde]
485pub struct MainInstantiateMsg {
487 pub admin: String,
489 pub min_participants: u32,
491 pub max_participants: u32,
493 pub default_time_control: TimeControlMode,
495 pub fee_amount: Option<Uint128>,
497 pub reward_nft_contract: Option<String>,
499}
500
501#[cw_serde]
502pub enum MainExecuteMsg {
504 CreateLotteryFromNft {
507 title: String,
509 description: String,
511 nft_contract: String,
513 time_control: Option<TimeControlMode>,
515 reward: VotingReward,
517 },
518 ExecuteLottery {
520 session_id: String,
522 random_seed: Option<u64>,
524 },
525 CancelLottery {
527 session_id: String,
529 },
530
531 CreateRewardNft {
534 session_id: String,
536 },
537
538 UpdateConfig {
541 min_participants: Option<u32>,
543 max_participants: Option<u32>,
545 default_time_control: Option<TimeControlMode>,
547 fee_amount: Option<Uint128>,
549 },
550 PauseContract {},
552 ResumeContract {},
554}
555
556#[cw_serde]
557pub enum MainQueryMsg {
559 GetLotterySession { session_id: String },
561 GetLotteryResult { session_id: String },
563 ListSessions {
565 start_after: Option<String>,
567 limit: Option<u32>,
569 status: Option<LotteryStatus>,
571 },
572 GetContractStats {},
574 GetConfig {},
576 GetParticipants { session_id: String },
578 IsCommitmentPhaseEnded { session_id: String },
580 IsRevealPhaseEnded { session_id: String },
582 GetCurrentTime {},
584 GetCurrentHeight {},
586
587 GetNftParticipants { nft_contract: String },
590 IsNftParticipant {
592 nft_contract: String,
593 participant: String,
594 },
595 GetNftVotingStatus { nft_contract: String },
597}
598
599#[cw_serde]
600pub struct Config {
602 pub admin: Addr,
604 pub min_participants: u32,
606 pub max_participants: u32,
608 pub default_time_control: TimeControlMode,
610 pub fee_amount: Option<Uint128>,
612 pub paused: bool,
614 pub reward_nft_contract: Option<String>,
616}
617
618#[cw_serde]
619pub struct ContractStats {
621 pub total_sessions: u64,
623 pub active_sessions: u64,
625 pub completed_sessions: u64,
627 pub cancelled_sessions: u64,
629 pub total_participants: u64,
631}
632
633#[cw_serde]
636pub struct RewardNftMetadata {
638 pub name: String,
640 pub description: String,
642 pub image_uri: Option<String>,
644 pub external_link: Option<String>,
646 pub attributes: Option<Vec<RewardNftAttribute>>,
648 pub background_color: Option<String>,
650 pub animation_url: Option<String>,
652 pub youtube_url: Option<String>,
654 pub minted_at: Timestamp,
656 pub lottery_session_id: String,
658 pub winner: String,
660}
661
662#[cw_serde]
663pub struct RewardNftAttribute {
665 pub trait_type: String,
667 pub value: String,
669 pub display_type: Option<String>,
671}
672
673#[cw_serde]
674pub enum RewardExecuteMsg {
676 MintReward {
678 token_id: String,
680 winner: String,
682 lottery_session_id: String,
684 metadata: RewardNftMetadata,
686 },
687}
688
689#[cw_serde]
692pub struct SingleTargetParams {
694 pub participants: Vec<String>,
696 pub vote_values: Option<Vec<u64>>,
698 pub random_seed: Option<u64>,
700 pub weights: Option<Vec<u32>>,
702}
703
704#[cw_serde]
705pub struct SingleTargetResult {
707 pub winner: String,
709 pub random_seed: u64,
711 pub result_hash: String,
713 pub random_number: u64,
715 pub total_participants: u32,
717}
718
719#[cw_serde]
722pub enum ContractError {
724 Unauthorized,
726 SessionNotFound,
728 ParticipantNotFound,
730 InvalidState,
732 TimeNotReached,
734 AlreadyVoted,
736 InvalidCommitment,
738 InsufficientParticipants,
740 TooManyParticipants,
742 ContractPaused,
744 InvalidAddress,
746 DuplicateParticipant,
748 CannotRemoveVotedParticipant,
750 MaximumParticipantsReached,
752 RewardNftNotConfigured,
754 OnlyAdminCanExecute,
756 OnlyCreatorCanCancel,
758 CannotCancelCompletedLottery,
760 CannotCreateRewardAfterCompletion,
762 NotAllParticipantsRevealed,
764 InvalidCommitmentVerification,
766 MustSubmitCommitmentFirst,
768 CommitmentAlreadySubmitted,
770 RevealAlreadySubmitted,
772 SessionNotActive,
774 TransfersLocked,
776 OnlyAdminCanSetLock,
778 OnlyAdminCanSetSessionActive,
780 OnlyAdminCanAddParticipants,
782 OnlyAdminCanRemoveParticipants,
784 OnlyAdminCanUpdateConfig,
786 OnlyAdminCanPauseContract,
788 OnlyAdminCanResumeContract,
790 OnlyAdminCanCreateRewardNft,
792 Generic(String),
794}
795
796impl From<ContractError> for cosmwasm_std::StdError {
797 fn from(err: ContractError) -> Self {
799 match err {
800 ContractError::Unauthorized => StdError::generic_err("Unauthorized"),
801 ContractError::SessionNotFound => StdError::generic_err("Session not found"),
802 ContractError::ParticipantNotFound => StdError::generic_err("Participant not found"),
803 ContractError::InvalidState => StdError::generic_err("Invalid state"),
804 ContractError::TimeNotReached => StdError::generic_err("Time not reached"),
805 ContractError::AlreadyVoted => StdError::generic_err("Already voted"),
806 ContractError::InvalidCommitment => StdError::generic_err("Invalid commitment"),
807 ContractError::InsufficientParticipants => {
808 StdError::generic_err("Insufficient participants")
809 }
810 ContractError::TooManyParticipants => StdError::generic_err("Too many participants"),
811 ContractError::ContractPaused => StdError::generic_err("Contract is paused"),
812 ContractError::InvalidAddress => StdError::generic_err("Invalid address"),
813 ContractError::DuplicateParticipant => StdError::generic_err("Duplicate participant"),
814 ContractError::CannotRemoveVotedParticipant => {
815 StdError::generic_err("Cannot remove voted participant")
816 }
817 ContractError::MaximumParticipantsReached => {
818 StdError::generic_err("Maximum participants reached")
819 }
820 ContractError::RewardNftNotConfigured => {
821 StdError::generic_err("Reward NFT not configured")
822 }
823 ContractError::OnlyAdminCanExecute => StdError::generic_err("Only admin can execute"),
824 ContractError::OnlyCreatorCanCancel => StdError::generic_err("Only creator can cancel"),
825 ContractError::CannotCancelCompletedLottery => {
826 StdError::generic_err("Cannot cancel completed lottery")
827 }
828 ContractError::CannotCreateRewardAfterCompletion => {
829 StdError::generic_err("Cannot create reward after completion")
830 }
831 ContractError::NotAllParticipantsRevealed => {
832 StdError::generic_err("Not all participants revealed")
833 }
834 ContractError::InvalidCommitmentVerification => {
835 StdError::generic_err("Invalid commitment verification")
836 }
837 ContractError::MustSubmitCommitmentFirst => {
838 StdError::generic_err("Must submit commitment first")
839 }
840 ContractError::CommitmentAlreadySubmitted => {
841 StdError::generic_err("Commitment already submitted")
842 }
843 ContractError::RevealAlreadySubmitted => {
844 StdError::generic_err("Reveal already submitted")
845 }
846 ContractError::SessionNotActive => StdError::generic_err("Session not active"),
847 ContractError::TransfersLocked => StdError::generic_err("Transfers locked"),
848 ContractError::OnlyAdminCanSetLock => StdError::generic_err("Only admin can set lock"),
849 ContractError::OnlyAdminCanSetSessionActive => {
850 StdError::generic_err("Only admin can set session active")
851 }
852 ContractError::OnlyAdminCanAddParticipants => {
853 StdError::generic_err("Only admin can add participants")
854 }
855 ContractError::OnlyAdminCanRemoveParticipants => {
856 StdError::generic_err("Only admin can remove participants")
857 }
858 ContractError::OnlyAdminCanUpdateConfig => {
859 StdError::generic_err("Only admin can update config")
860 }
861 ContractError::OnlyAdminCanPauseContract => {
862 StdError::generic_err("Only admin can pause contract")
863 }
864 ContractError::OnlyAdminCanResumeContract => {
865 StdError::generic_err("Only admin can resume contract")
866 }
867 ContractError::OnlyAdminCanCreateRewardNft => {
868 StdError::generic_err("Only admin can create reward NFT")
869 }
870 ContractError::Generic(msg) => StdError::generic_err(msg),
871 }
872 }
873}
874
875pub fn verify_commitment(commitment_hash: &str, vote_data: &[u8], salt: &str) -> bool {
879 use sha2::{Digest, Sha256};
880 let mut hasher = Sha256::new();
881 hasher.update(vote_data);
882 hasher.update(salt.as_bytes());
883 let calculated_hash = format!("{:x}", hasher.finalize());
884 calculated_hash == commitment_hash
885}
886
887pub fn generate_commitment_hash(vote_data: &[u8], salt: &str) -> String {
889 use sha2::{Digest, Sha256};
890 let mut hasher = Sha256::new();
891 hasher.update(vote_data);
892 hasher.update(salt.as_bytes());
893 format!("{:x}", hasher.finalize())
894}
895
896pub fn generate_random_seed(block_hash: &str, timestamp: u64) -> u64 {
898 use sha2::{Digest, Sha256};
899 let mut hasher = Sha256::new();
900 hasher.update(block_hash.as_bytes());
901 hasher.update(timestamp.to_be_bytes());
902 let hash = hasher.finalize();
903
904 let mut seed_bytes = [0u8; 8];
905 seed_bytes.copy_from_slice(&hash[0..8]);
906 u64::from_be_bytes(seed_bytes)
907}
908
909pub fn validate_unique_participants(participants: &[String]) -> Result<(), ContractError> {
911 for i in 0..participants.len() {
912 for j in (i + 1)..participants.len() {
913 if participants[i] == participants[j] {
914 return Err(ContractError::DuplicateParticipant);
915 }
916 }
917 }
918 Ok(())
919}
920
921pub fn validate_participant_count(
923 participants: &[String],
924 min_participants: u32,
925 max_participants: u32,
926) -> Result<(), ContractError> {
927 let count = participants.len() as u32;
928
929 if count < min_participants {
930 return Err(ContractError::InsufficientParticipants);
931 }
932
933 if count > max_participants {
934 return Err(ContractError::TooManyParticipants);
935 }
936
937 Ok(())
938}
939
940pub fn create_default_reward() -> VotingReward {
942 VotingReward {
943 name: "Default Reward".to_string(),
944 description: "A default reward for lottery winners".to_string(),
945 image_uri: None,
946 external_link: None,
947 attributes: None,
948 background_color: None,
949 animation_url: None,
950 youtube_url: None,
951 }
952}
953
954pub fn create_example_reward() -> VotingReward {
956 VotingReward {
957 name: "Lucky Winner NFT".to_string(),
958 description: "A special NFT for the lottery winner".to_string(),
959 image_uri: Some("https://example.com/winner-nft.png".to_string()),
960 external_link: Some("https://example.com/winner-info".to_string()),
961 attributes: Some(vec![
962 RewardAttribute {
963 trait_type: "Rarity".to_string(),
964 value: "Legendary".to_string(),
965 display_type: Some("string".to_string()),
966 },
967 RewardAttribute {
968 trait_type: "Power Level".to_string(),
969 value: "100".to_string(),
970 display_type: Some("number".to_string()),
971 },
972 ]),
973 background_color: Some("#FFD700".to_string()),
974 animation_url: Some("https://example.com/winner-animation.mp4".to_string()),
975 youtube_url: Some("https://youtube.com/watch?v=winner".to_string()),
976 }
977}