Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,119 changes: 1,119 additions & 0 deletions contracts/Cargo.lock

Large diffs are not rendered by default.

34 changes: 17 additions & 17 deletions contracts/dao-governance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ink = { version = "4.2.1", default-features = false }
ink_lang = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_lang" }
ink_storage = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_storage" }
ink_env = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_env" }
ink_prelude = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_prelude" }
ink_primitives = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_primitives" }
ink = { version = "4.2.0", default-features = false }
ink_metadata = { version = "4.2.0", default-features = false }
ink_storage = { version = "4.2.0", default-features = false }
ink_prelude = { version = "4.2.0", default-features = false }
scale = { package = "parity-scale-codec", version = "3.6.5", default-features = false, features = ["derive"] }
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "1.0"

parity-scale-codec = { version = "3.6", default-features = false }
scale-info = { version = "2.10", features = ["derive"], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = { version = "1.0", optional = true }
[dev-dependencies]
ink_env = { version = "4.2.0", default-features = false }

[lib]
name = "dao_governance"
Expand All @@ -24,13 +24,13 @@ crate-type = ["cdylib", "rlib"]
[features]
default = ["std"]
std = [
"ink/std",
"ink_lang/std",
"ink_storage/std",
"parity-scale-codec/std",
"scale-info/std",
"serde",
"thiserror",
"ink/std",
"ink_metadata/std",
"ink_storage/std",
"ink_prelude/std",
"scale/std",
"scale-info/std",
"serde/std",
]

# For testing with cargo-contract
Expand Down
41 changes: 0 additions & 41 deletions contracts/dao-governance/src/dao_tests.rs

This file was deleted.

160 changes: 111 additions & 49 deletions contracts/dao-governance/src/governance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;

use std::collections::HashMap;

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum Role {
Expand All @@ -9,102 +9,164 @@ pub enum Role {
Admin,
}


#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserIdentity {
pub address: String,
pub address: String,
pub role: Role,
}


#[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum GovernanceError {
#[error("You are not authorized to perform this action.")]
Unauthorized,

#[error("The requested proposal was not found.")]
ProposalNotFound,

#[error("Invalid operation.")]
InvalidOperation,
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum ProposalStatus {
Active,
Accepted,
Rejected,
Expired,
}


#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Proposal {
pub id: u64,
pub title: String,
pub description: String,
pub proposer: String,
pub proposer: String,
pub votes_for: u32,
pub votes_against: u32,
pub status: ProposalStatus,
pub end_time: u64, // Unix timestamp for voting deadline
}

#[derive(Debug, thiserror::Error)]
pub enum GovernanceError {
#[error("You are not authorized to perform this action.")]
Unauthorized,
#[error("The requested proposal was not found.")]
ProposalNotFound,
#[error("Voting period has expired.")]
VotingExpired,
#[error("User has already voted.")]
AlreadyVoted,
#[error("Invalid end time for proposal.")]
InvalidEndTime,
}

pub struct GovernanceDAO {
pub proposals: Vec<Proposal>,
proposal_counter: u64,
user_roles: HashMap<String, Role>,
votes: HashMap<(u64, String), bool>,
quorum: u32,
}

impl GovernanceDAO {

pub fn new() -> Self {
pub fn new(quorum: u32) -> Self {
Self {
proposals: Vec::new(),
proposal_counter: 0,
user_roles: HashMap::new(),
votes: HashMap::new(),
quorum,
}
}

pub fn set_role(&mut self, caller: &UserIdentity, user: UserIdentity) -> Result<(), GovernanceError> {
if !matches!(caller.role, Role::Admin) {
return Err(GovernanceError::Unauthorized);
}
self.user_roles.insert(user.address.clone(), user.role);
Ok(())
}


pub fn submit_proposal(
&mut self,
user: &UserIdentity,
title: String,
description: String,
end_time: u64,
current_time: u64,
) -> Result<(), GovernanceError> {
if matches!(user.role, Role::Tutor | Role::Admin) {
self.proposal_counter += 1;
let proposal = Proposal {
id: self.proposal_counter,
title,
description,
proposer: user.address.clone(),
votes_for: 0,
votes_against: 0,
};
self.proposals.push(proposal);
Ok(())
} else {
Err(GovernanceError::Unauthorized)
if !matches!(user.role, Role::Tutor | Role::Admin) {
return Err(GovernanceError::Unauthorized);
}
if end_time <= current_time {
return Err(GovernanceError::InvalidEndTime);
}

self.proposal_counter += 1;
let proposal = Proposal {
id: self.proposal_counter,
title,
description,
proposer: user.address.clone(),
votes_for: 0,
votes_against: 0,
status: ProposalStatus::Active,
end_time,
};
self.proposals.push(proposal);
Ok(())
}


pub fn vote_on_proposal(
&mut self,
user: &UserIdentity,
proposal_id: u64,
support: bool,
support: bool,
current_time: u64,
) -> Result<(), GovernanceError> {
if matches!(user.role, Role::Student | Role::Tutor) {
let proposal = self.proposals
.iter_mut()
.find(|p| p.id == proposal_id)
.ok_or(GovernanceError::ProposalNotFound)?;

if support {
proposal.votes_for += 1;
} else {
proposal.votes_against += 1;
}
Ok(())
if !matches!(user.role, Role::Student | Role::Tutor) {
return Err(GovernanceError::Unauthorized);
}

let proposal = self.proposals
.iter_mut()
.find(|p| p.id == proposal_id)
.ok_or(GovernanceError::ProposalNotFound)?;

if proposal.status != ProposalStatus::Active {
return Err(GovernanceError::VotingExpired);
}
if current_time > proposal.end_time {
return Err(GovernanceError::VotingExpired);
}

let vote_key = (proposal_id, user.address.clone());
if self.votes.contains_key(&vote_key) {
return Err(GovernanceError::AlreadyVoted);
}

if support {
proposal.votes_for += 1;
} else {
Err(GovernanceError::Unauthorized)
proposal.votes_against += 1;
}
self.votes.insert(vote_key, support);
Ok(())
}

pub fn finalize_proposal(&mut self, proposal_id: u64, current_time: u64) -> Result<(), GovernanceError> {
let proposal = self.proposals
.iter_mut()
.find(|p| p.id == proposal_id)
.ok_or(GovernanceError::ProposalNotFound)?;

if proposal.status != ProposalStatus::Active {
return Err(GovernanceError::VotingExpired);
}
if current_time <= proposal.end_time {
return Err(GovernanceError::VotingExpired);
}

let total_votes = proposal.votes_for + proposal.votes_against;
proposal.status = if total_votes >= self.quorum && proposal.votes_for > proposal.votes_against {
ProposalStatus::Accepted
} else {
ProposalStatus::Rejected
};
Ok(())
}


pub fn list_proposals(&self) -> &Vec<Proposal> {
&self.proposals
}
}

Loading
Loading