use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Binary, StdError, Timestamp, Uint128};
use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey};
#[cw_serde]
pub enum DDAlgorithmType {
SingleTargetLottery,
MultiTargetLottery,
MultiLevelLottery,
MultiTargetVoting,
HierarchicalDistribution,
SingleTargetWithCompensation,
PokerShuffleOneTime,
PokerShuffleSequential,
}
impl DDAlgorithmType {
pub fn as_str(&self) -> &'static str {
match self {
DDAlgorithmType::SingleTargetLottery => "single_target_lottery",
DDAlgorithmType::MultiTargetLottery => "multi_target_lottery",
DDAlgorithmType::MultiLevelLottery => "multi_level_lottery",
DDAlgorithmType::MultiTargetVoting => "multi_target_voting",
DDAlgorithmType::HierarchicalDistribution => "hierarchical_distribution",
DDAlgorithmType::SingleTargetWithCompensation => "single_target_with_compensation",
DDAlgorithmType::PokerShuffleOneTime => "poker_shuffle_one_time",
DDAlgorithmType::PokerShuffleSequential => "poker_shuffle_sequential",
}
}
pub fn all() -> &'static [DDAlgorithmType] {
use DDAlgorithmType::*;
const VARIANTS: [DDAlgorithmType; 8] = [
SingleTargetLottery,
MultiTargetLottery,
MultiLevelLottery,
MultiTargetVoting,
HierarchicalDistribution,
SingleTargetWithCompensation,
PokerShuffleOneTime,
PokerShuffleSequential,
];
&VARIANTS
}
}
impl<'a> PrimaryKey<'a> for DDAlgorithmType {
type Prefix = ();
type SubPrefix = ();
type Suffix = Self;
type SuperSuffix = Self;
fn key(&self) -> Vec<Key<'_>> {
vec![Key::Ref(self.as_str().as_bytes())]
}
}
impl KeyDeserialize for DDAlgorithmType {
type Output = Self;
const KEY_ELEMS: u16 = 1;
fn from_vec(value: Vec<u8>) -> Result<Self, StdError> {
let s = String::from_utf8(value)
.map_err(|_| StdError::generic_err("DDAlgorithmType: invalid utf8"))?;
match s.as_str() {
"single_target_lottery" => Ok(DDAlgorithmType::SingleTargetLottery),
"multi_target_lottery" => Ok(DDAlgorithmType::MultiTargetLottery),
"multi_level_lottery" => Ok(DDAlgorithmType::MultiLevelLottery),
"multi_target_voting" => Ok(DDAlgorithmType::MultiTargetVoting),
"hierarchical_distribution" => Ok(DDAlgorithmType::HierarchicalDistribution),
"single_target_with_compensation" => Ok(DDAlgorithmType::SingleTargetWithCompensation),
"poker_shuffle_one_time" => Ok(DDAlgorithmType::PokerShuffleOneTime),
"poker_shuffle_sequential" => Ok(DDAlgorithmType::PokerShuffleSequential),
_ => Err(StdError::generic_err("DDAlgorithmType: unknown variant")),
}
}
}
#[cw_serde]
pub struct RewardAttribute {
pub trait_type: String,
pub value: String,
pub display_type: Option<String>,
}
#[cw_serde]
pub struct VotingReward {
pub name: String,
pub description: String,
pub image_uri: Option<String>,
pub external_link: Option<String>,
pub attributes: Option<Vec<RewardAttribute>>,
pub background_color: Option<String>,
pub animation_url: Option<String>,
pub youtube_url: Option<String>,
}
#[cw_serde]
pub struct RewardMetadata {
pub name: String,
pub description: String,
pub image_uri: Option<String>,
pub external_link: Option<String>,
pub attributes: Option<Vec<RewardAttribute>>,
pub background_color: Option<String>,
pub animation_url: Option<String>,
pub youtube_url: Option<String>,
pub minted_at: Timestamp,
pub lottery_session_id: String,
pub winner: String,
}
#[cw_serde]
pub enum DistributionStrategy {
Random,
ByRank,
ByWeight,
Custom(String),
}
#[cw_serde]
pub struct RewardDistribution {
pub winner: String,
pub token_id: String,
}
#[cw_serde]
pub enum TimeControlMode {
BlockHeight {
commitment_height: u64,
reveal_height: u64,
},
TimeInterval {
commitment_duration: u64,
reveal_duration: u64,
},
}
#[cw_serde]
pub enum LotteryStatus {
Created,
CommitmentPhase,
RevealPhase,
Completed,
Cancelled,
}
#[cw_serde]
pub struct LotterySessionInfo {
pub session_id: String,
pub title: String,
pub description: String,
pub creator: String,
pub created_at: Timestamp,
pub total_rewards: u32,
pub minted_rewards: u32,
}
#[cw_serde]
pub struct LotterySession {
pub session_id: String,
pub title: String,
pub description: String,
pub participants: Vec<String>,
pub nft_contract: Option<String>,
pub creator: String,
pub status: LotteryStatus,
pub created_at: Timestamp,
pub created_height: u64,
pub time_control: TimeControlMode,
pub commitment_deadline: Option<Timestamp>,
pub reveal_deadline: Option<Timestamp>,
pub commitment_deadline_height: Option<u64>,
pub reveal_deadline_height: Option<u64>,
pub reward: VotingReward,
pub result: Option<LotteryResult>,
}
#[cw_serde]
pub struct LotteryResult {
pub session_id: String,
pub winner: String,
pub random_seed: u64,
pub random_number: u64,
pub result_hash: String,
pub total_participants: u32,
pub executed_at: Timestamp,
pub algorithm_type: String,
}
#[cw_serde]
pub struct NftInstantiateMsg {
pub name: String,
pub symbol: String,
pub minter: String,
pub participants: Vec<String>,
pub max_participants: u32,
pub session_active: Option<bool>,
}
#[cw_serde]
pub enum NftExecuteMsg {
SubmitCommitment {
commitment_hash: String,
salt: String,
},
SubmitReveal {
vote_data: Binary,
salt: String,
},
AddParticipant {
participant: String,
},
AddParticipants {
participants: Vec<String>,
},
RemoveParticipant {
participant: String,
},
SetTransfersLocked {
locked: bool,
},
SetSessionActive {
active: bool,
},
TransferNft {
recipient: String,
token_id: String,
},
}
#[cw_serde]
pub enum NftQueryMsg {
GetParticipants {},
IsParticipant { participant: String },
GetParticipantCount {},
GetMaxParticipants {},
GetParticipantIndex { address: String },
GetCommitment { participant: String },
GetReveal { participant: String },
GetAllCommitments {},
GetAllReveals {},
HasCommitment { participant: String },
HasReveal { participant: String },
GetSessionActive {},
GetTransfersLocked {},
GetSessionMeta {},
OwnerOf { token_id: String },
GetCurrentTime {},
GetCurrentHeight {},
CanAddParticipants { count: u32 },
GetVotingStatus {},
}
#[cw_serde]
pub struct NftSessionMeta {
pub participants: Vec<String>,
pub max_participants: u32,
pub session_active: bool,
}
#[cw_serde]
pub struct CommitmentInfo {
pub participant: String,
pub commitment_hash: String,
pub salt: String,
pub submitted_at: u64,
}
#[cw_serde]
pub struct RevealInfo {
pub participant: String,
pub vote_data: Binary,
pub salt: String,
pub submitted_at: u64,
}
#[cw_serde]
pub struct VotingStatus {
pub total_participants: u32,
pub commitments_count: u32,
pub reveals_count: u32,
pub session_active: bool,
pub all_committed: bool,
pub all_revealed: bool,
}
#[cw_serde]
pub struct MainInstantiateMsg {
pub admin: String,
pub min_participants: u32,
pub max_participants: u32,
pub default_time_control: TimeControlMode,
pub fee_amount: Option<Uint128>,
pub reward_nft_contract: Option<String>,
}
#[cw_serde]
pub enum MainExecuteMsg {
CreateLotteryFromNft {
title: String,
description: String,
nft_contract: String,
time_control: Option<TimeControlMode>,
reward: VotingReward,
},
ExecuteLottery {
session_id: String,
random_seed: Option<u64>,
},
CancelLottery {
session_id: String,
},
CreateRewardNft {
session_id: String,
},
UpdateConfig {
min_participants: Option<u32>,
max_participants: Option<u32>,
default_time_control: Option<TimeControlMode>,
fee_amount: Option<Uint128>,
},
PauseContract {},
ResumeContract {},
}
#[cw_serde]
pub enum MainQueryMsg {
GetLotterySession { session_id: String },
GetLotteryResult { session_id: String },
ListSessions {
start_after: Option<String>,
limit: Option<u32>,
status: Option<LotteryStatus>,
},
GetContractStats {},
GetConfig {},
GetParticipants { session_id: String },
IsCommitmentPhaseEnded { session_id: String },
IsRevealPhaseEnded { session_id: String },
GetCurrentTime {},
GetCurrentHeight {},
GetNftParticipants { nft_contract: String },
IsNftParticipant {
nft_contract: String,
participant: String,
},
GetNftVotingStatus { nft_contract: String },
}
#[cw_serde]
pub struct Config {
pub admin: Addr,
pub min_participants: u32,
pub max_participants: u32,
pub default_time_control: TimeControlMode,
pub fee_amount: Option<Uint128>,
pub paused: bool,
pub reward_nft_contract: Option<String>,
}
#[cw_serde]
pub struct ContractStats {
pub total_sessions: u64,
pub active_sessions: u64,
pub completed_sessions: u64,
pub cancelled_sessions: u64,
pub total_participants: u64,
}
#[cw_serde]
pub struct RewardNftMetadata {
pub name: String,
pub description: String,
pub image_uri: Option<String>,
pub external_link: Option<String>,
pub attributes: Option<Vec<RewardNftAttribute>>,
pub background_color: Option<String>,
pub animation_url: Option<String>,
pub youtube_url: Option<String>,
pub minted_at: Timestamp,
pub lottery_session_id: String,
pub winner: String,
}
#[cw_serde]
pub struct RewardNftAttribute {
pub trait_type: String,
pub value: String,
pub display_type: Option<String>,
}
#[cw_serde]
pub enum RewardExecuteMsg {
MintReward {
token_id: String,
winner: String,
lottery_session_id: String,
metadata: RewardNftMetadata,
},
}
#[cw_serde]
pub struct SingleTargetParams {
pub participants: Vec<String>,
pub vote_values: Option<Vec<u64>>,
pub random_seed: Option<u64>,
pub weights: Option<Vec<u32>>,
}
#[cw_serde]
pub struct SingleTargetResult {
pub winner: String,
pub random_seed: u64,
pub result_hash: String,
pub random_number: u64,
pub total_participants: u32,
}
#[cw_serde]
pub enum ContractError {
Unauthorized,
SessionNotFound,
ParticipantNotFound,
InvalidState,
TimeNotReached,
AlreadyVoted,
InvalidCommitment,
InsufficientParticipants,
TooManyParticipants,
ContractPaused,
InvalidAddress,
DuplicateParticipant,
CannotRemoveVotedParticipant,
MaximumParticipantsReached,
RewardNftNotConfigured,
OnlyAdminCanExecute,
OnlyCreatorCanCancel,
CannotCancelCompletedLottery,
CannotCreateRewardAfterCompletion,
NotAllParticipantsRevealed,
InvalidCommitmentVerification,
MustSubmitCommitmentFirst,
CommitmentAlreadySubmitted,
RevealAlreadySubmitted,
SessionNotActive,
TransfersLocked,
OnlyAdminCanSetLock,
OnlyAdminCanSetSessionActive,
OnlyAdminCanAddParticipants,
OnlyAdminCanRemoveParticipants,
OnlyAdminCanUpdateConfig,
OnlyAdminCanPauseContract,
OnlyAdminCanResumeContract,
OnlyAdminCanCreateRewardNft,
Generic(String),
}
impl From<ContractError> for cosmwasm_std::StdError {
fn from(err: ContractError) -> Self {
match err {
ContractError::Unauthorized => StdError::generic_err("Unauthorized"),
ContractError::SessionNotFound => StdError::generic_err("Session not found"),
ContractError::ParticipantNotFound => StdError::generic_err("Participant not found"),
ContractError::InvalidState => StdError::generic_err("Invalid state"),
ContractError::TimeNotReached => StdError::generic_err("Time not reached"),
ContractError::AlreadyVoted => StdError::generic_err("Already voted"),
ContractError::InvalidCommitment => StdError::generic_err("Invalid commitment"),
ContractError::InsufficientParticipants => {
StdError::generic_err("Insufficient participants")
}
ContractError::TooManyParticipants => StdError::generic_err("Too many participants"),
ContractError::ContractPaused => StdError::generic_err("Contract is paused"),
ContractError::InvalidAddress => StdError::generic_err("Invalid address"),
ContractError::DuplicateParticipant => StdError::generic_err("Duplicate participant"),
ContractError::CannotRemoveVotedParticipant => {
StdError::generic_err("Cannot remove voted participant")
}
ContractError::MaximumParticipantsReached => {
StdError::generic_err("Maximum participants reached")
}
ContractError::RewardNftNotConfigured => {
StdError::generic_err("Reward NFT not configured")
}
ContractError::OnlyAdminCanExecute => StdError::generic_err("Only admin can execute"),
ContractError::OnlyCreatorCanCancel => StdError::generic_err("Only creator can cancel"),
ContractError::CannotCancelCompletedLottery => {
StdError::generic_err("Cannot cancel completed lottery")
}
ContractError::CannotCreateRewardAfterCompletion => {
StdError::generic_err("Cannot create reward after completion")
}
ContractError::NotAllParticipantsRevealed => {
StdError::generic_err("Not all participants revealed")
}
ContractError::InvalidCommitmentVerification => {
StdError::generic_err("Invalid commitment verification")
}
ContractError::MustSubmitCommitmentFirst => {
StdError::generic_err("Must submit commitment first")
}
ContractError::CommitmentAlreadySubmitted => {
StdError::generic_err("Commitment already submitted")
}
ContractError::RevealAlreadySubmitted => {
StdError::generic_err("Reveal already submitted")
}
ContractError::SessionNotActive => StdError::generic_err("Session not active"),
ContractError::TransfersLocked => StdError::generic_err("Transfers locked"),
ContractError::OnlyAdminCanSetLock => StdError::generic_err("Only admin can set lock"),
ContractError::OnlyAdminCanSetSessionActive => {
StdError::generic_err("Only admin can set session active")
}
ContractError::OnlyAdminCanAddParticipants => {
StdError::generic_err("Only admin can add participants")
}
ContractError::OnlyAdminCanRemoveParticipants => {
StdError::generic_err("Only admin can remove participants")
}
ContractError::OnlyAdminCanUpdateConfig => {
StdError::generic_err("Only admin can update config")
}
ContractError::OnlyAdminCanPauseContract => {
StdError::generic_err("Only admin can pause contract")
}
ContractError::OnlyAdminCanResumeContract => {
StdError::generic_err("Only admin can resume contract")
}
ContractError::OnlyAdminCanCreateRewardNft => {
StdError::generic_err("Only admin can create reward NFT")
}
ContractError::Generic(msg) => StdError::generic_err(msg),
}
}
}
pub fn verify_commitment(commitment_hash: &str, vote_data: &[u8], salt: &str) -> bool {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(vote_data);
hasher.update(salt.as_bytes());
let calculated_hash = format!("{:x}", hasher.finalize());
calculated_hash == commitment_hash
}
pub fn generate_commitment_hash(vote_data: &[u8], salt: &str) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(vote_data);
hasher.update(salt.as_bytes());
format!("{:x}", hasher.finalize())
}
pub fn generate_random_seed(block_hash: &str, timestamp: u64) -> u64 {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(block_hash.as_bytes());
hasher.update(timestamp.to_be_bytes());
let hash = hasher.finalize();
let mut seed_bytes = [0u8; 8];
seed_bytes.copy_from_slice(&hash[0..8]);
u64::from_be_bytes(seed_bytes)
}
pub fn validate_unique_participants(participants: &[String]) -> Result<(), ContractError> {
for i in 0..participants.len() {
for j in (i + 1)..participants.len() {
if participants[i] == participants[j] {
return Err(ContractError::DuplicateParticipant);
}
}
}
Ok(())
}
pub fn validate_participant_count(
participants: &[String],
min_participants: u32,
max_participants: u32,
) -> Result<(), ContractError> {
let count = participants.len() as u32;
if count < min_participants {
return Err(ContractError::InsufficientParticipants);
}
if count > max_participants {
return Err(ContractError::TooManyParticipants);
}
Ok(())
}
pub fn create_default_reward() -> VotingReward {
VotingReward {
name: "Default Reward".to_string(),
description: "A default reward for lottery winners".to_string(),
image_uri: None,
external_link: None,
attributes: None,
background_color: None,
animation_url: None,
youtube_url: None,
}
}
pub fn create_example_reward() -> VotingReward {
VotingReward {
name: "Lucky Winner NFT".to_string(),
description: "A special NFT for the lottery winner".to_string(),
image_uri: Some("https://example.com/winner-nft.png".to_string()),
external_link: Some("https://example.com/winner-info".to_string()),
attributes: Some(vec![
RewardAttribute {
trait_type: "Rarity".to_string(),
value: "Legendary".to_string(),
display_type: Some("string".to_string()),
},
RewardAttribute {
trait_type: "Power Level".to_string(),
value: "100".to_string(),
display_type: Some("number".to_string()),
},
]),
background_color: Some("#FFD700".to_string()),
animation_url: Some("https://example.com/winner-animation.mp4".to_string()),
youtube_url: Some("https://youtube.com/watch?v=winner".to_string()),
}
}