From 8c65bad315d82a569f4fa9df5b4e2e26f14b06e5 Mon Sep 17 00:00:00 2001 From: nagxsan Date: Thu, 1 May 2025 00:57:27 +0530 Subject: [PATCH 1/2] feat: add logic for voting operation functions --- contracts/boundless/src/lib.rs | 1 + contracts/boundless/src/logic/voting.rs | 163 +++++++++++------------- contracts/boundless/src/tests/voting.rs | 96 +++++++------- 3 files changed, 123 insertions(+), 137 deletions(-) diff --git a/contracts/boundless/src/lib.rs b/contracts/boundless/src/lib.rs index c482c1c..4333990 100644 --- a/contracts/boundless/src/lib.rs +++ b/contracts/boundless/src/lib.rs @@ -7,6 +7,7 @@ pub use logic::*; mod datatypes; mod interface; mod logic; +mod tests; #[contract] pub struct BoundlessContract; diff --git a/contracts/boundless/src/logic/voting.rs b/contracts/boundless/src/logic/voting.rs index 88b4f17..4b63396 100644 --- a/contracts/boundless/src/logic/voting.rs +++ b/contracts/boundless/src/logic/voting.rs @@ -3,7 +3,7 @@ use crate::{ interface::{ProjectManagement, VotingOperations}, BoundlessContract, BoundlessContractArgs, BoundlessContractClient, }; -use soroban_sdk::{contractimpl, symbol_short, Address, Env, String}; +use soroban_sdk::{contractimpl, symbol_short, Address, Env, String, Vec}; #[contractimpl] impl VotingOperations for BoundlessContract { @@ -13,60 +13,46 @@ impl VotingOperations for BoundlessContract { voter: Address, vote_value: i32, ) -> Result<(), BoundlessError> { - // let mut project: Project = env - // .storage() - // .persistent() - // .get(&DataKey::Project(project_id.clone())) - // .ok_or(BoundlessError::NotFound)?; - // if project.status != ProjectStatus::Voting { - // return Err(BoundlessError::InvalidOperation); - // } - // if vote_value != 1 && vote_value != -1 { - // return Err(BoundlessError::InvalidVote); - // } - // if project.creator == voter { - // return Err(BoundlessError::InvalidOperation); - // } - // if project.is_closed { - // return Err(BoundlessError::InvalidOperation); - // } - // if project.voting_deadline < env.ledger().timestamp() { - // return Err(BoundlessError::VotingPeriodEnded); - // } - // if project.votes.iter().any(|(voter, _)| voter == voter) { - // return Err(BoundlessError::AlreadyVoted); - // } - // project.votes.push_back((voter, vote_value)); - - // let total_votes = project.votes.len(); - // let positive_votes = project.votes.iter().filter(|(_, vote)| *vote == 1).count(); - // let voting_threshold: u32 = 1; // Define your threshold here - - // if (total_votes as u32) >= voting_threshold - // && positive_votes > (total_votes / 2).try_into().unwrap() - // { - // // Change project status to funding().try_into().unwrap() - // project.status = ProjectStatus::Funding; - // project.funding_deadline = env.ledger().timestamp() + VOTING_PERIOD_LEDGERS as u64; - - // // Publish event for status change - // env.events().publish( - // ( - // DataKey::Project(project_id.clone()), - // symbol_short!("status"), - // ), - // (project.project_id.clone(), ProjectStatus::Funding), - // ); - // } - - // env.storage() - // .persistent() - // .set(&DataKey::Project(project_id.clone()), &project); - // env.events().publish( - // (DataKey::Project(project_id.clone()), symbol_short!("voted")), - // project.project_id, - // ); + let mut project: Project = env + .storage() + .persistent() + .get(&DataKey::Project(project_id.clone())) + .ok_or(BoundlessError::NotFound)?; + + if project.status != ProjectStatus::Voting { + return Err(BoundlessError::InvalidOperation); + } + + if project.is_closed { + return Err(BoundlessError::ProjectClosed); + } + + if project.voting_deadline < env.ledger().timestamp() { + return Err(BoundlessError::VotingPeriodEnded); + } + + if project.creator == voter { + return Err(BoundlessError::InvalidOperation); + } + + if project.votes.iter().any(|vote| vote.0 == voter) { + return Err(BoundlessError::AlreadyVoted); + } + + if vote_value != 1 && vote_value != -1 { + return Err(BoundlessError::InvalidVote); + } + + project.votes.push_back((voter, vote_value)); + + env.storage() + .persistent() + .set(&DataKey::Project(project_id.clone()), &project); + env.events().publish( + (DataKey::Project(project_id.clone()), symbol_short!("voted")), + project_id, + ); Ok(()) } fn withdraw_vote(env: Env, project_id: String, voter: Address) -> Result<(), BoundlessError> { @@ -75,66 +61,65 @@ impl VotingOperations for BoundlessContract { .persistent() .get(&DataKey::Project(project_id.clone())) .ok_or(BoundlessError::NotFound)?; + if project.status != ProjectStatus::Voting { return Err(BoundlessError::InvalidOperation); } - if project.creator == voter { - return Err(BoundlessError::InvalidOperation); - } if project.is_closed { - return Err(BoundlessError::InvalidOperation); + return Err(BoundlessError::ProjectClosed); } + if project.voting_deadline < env.ledger().timestamp() { return Err(BoundlessError::VotingPeriodEnded); } - // Find and remove the vote by the voter - let mut i = 0; - while i < project.votes.len() { - if project.votes.get_unchecked(i).0 == voter { - project.votes.remove(i); - break; - } - i += 1; + if project.creator == voter { + return Err(BoundlessError::InvalidOperation); + } + + if !project.votes.iter().any(|vote| vote.0 == voter) { + return Err(BoundlessError::NotVoted); + } + + if let Some(index) = project.votes.iter().position(|vote| vote.0 == voter) { + project.votes.remove(index as u32); } env.storage() .persistent() .set(&DataKey::Project(project_id.clone()), &project); + env.events().publish( ( DataKey::Project(project_id.clone()), symbol_short!("withdrawn"), ), - project.project_id, + project_id, ); + Ok(()) } fn has_voted(env: Env, project_id: String, voter: Address) -> Result { - // let project: Project = env - // .storage() - // .persistent() - // .get(&DataKey::Project(project_id.clone())) - // .ok_or(BoundlessError::NotFound)?; - // for vote in project.votes.iter() { - // if vote.0 == voter { - // return Ok(true); - // } - // } - Ok(false) + let project: Project = env + .storage() + .persistent() + .get(&DataKey::Project(project_id.clone())) + .ok_or(BoundlessError::NotFound)?; + + Ok(project.votes.iter().any(|vote| vote.0 == voter)) } fn get_vote(env: Env, project_id: String, voter: Address) -> Result { - // let project: Project = env - // .storage() - // .persistent() - // .get(&DataKey::Project(project_id.clone())) - // .ok_or(BoundlessError::NotFound)?; - // for vote in project.votes.iter() { - // if vote.0 == voter { - // return Ok(vote.1); - // } - // } - Ok(0) + let project: Project = env + .storage() + .persistent() + .get(&DataKey::Project(project_id.clone())) + .ok_or(BoundlessError::NotFound)?; + for vote in project.votes { + if vote.0 == voter { + return Ok(vote.1); + } + } + return Err(BoundlessError::NotVoted); } } diff --git a/contracts/boundless/src/tests/voting.rs b/contracts/boundless/src/tests/voting.rs index c401bf6..8ad2033 100644 --- a/contracts/boundless/src/tests/voting.rs +++ b/contracts/boundless/src/tests/voting.rs @@ -139,32 +139,32 @@ fn test_vote_project_funding_period_ended() { client.vote_project(&project_id, &creator, &vote_value); } -// #[test] -// fn test_withdraw_vote() { -// let env = Env::default(); -// let contract_id = env.register(BoundlessContract, ()); -// let client = BoundlessContractClient::new(&env, &contract_id); -// env.mock_all_auths(); -// let creator = Address::generate(&env); -// let project_id = String::from_str(&env, "test_project"); -// let metadata_uri = String::from_str(&env, "https://example.com/metadata"); -// let funding_target = 1000; -// let milestone_count = 5; +#[test] +fn test_withdraw_vote() { + let env = Env::default(); + let contract_id = env.register(BoundlessContract, ()); + let client = BoundlessContractClient::new(&env, &contract_id); + env.mock_all_auths(); + let creator = Address::generate(&env); + let project_id = String::from_str(&env, "test_project"); + let metadata_uri = String::from_str(&env, "https://example.com/metadata"); + let funding_target = 1000; + let milestone_count = 5; -// client.create_project( -// &project_id, -// &creator, -// &metadata_uri, -// &funding_target, -// &milestone_count, -// ); -// let voter = Address::generate(&env); -// let vote_value = 1; -// client.vote_project(&project_id, &voter, &vote_value); -// client.withdraw_vote(&project_id, &voter); -// let project = client.get_project(&project_id); -// assert_eq!(project.votes.len(), 0); -// } + client.create_project( + &project_id, + &creator, + &metadata_uri, + &funding_target, + &milestone_count, + ); + let voter = Address::generate(&env); + let vote_value = 1; + client.vote_project(&project_id, &voter, &vote_value); + client.withdraw_vote(&project_id, &voter); + let project = client.get_project(&project_id); + assert_eq!(project.votes.len(), 0); +} #[test] #[should_panic] @@ -295,27 +295,27 @@ fn test_has_voted() { assert_eq!(client.has_voted(&project_id, &voter), true); } -// #[test] -// fn test_get_vote() { -// let env = Env::default(); -// let contract_id = env.register(BoundlessContract, ()); -// let client = BoundlessContractClient::new(&env, &contract_id); -// env.mock_all_auths(); -// let creator = Address::generate(&env); -// let project_id = String::from_str(&env, "test_project"); -// let metadata_uri = String::from_str(&env, "https://example.com/metadata"); -// let funding_target = 1000; -// let milestone_count = 5; +#[test] +fn test_get_vote() { + let env = Env::default(); + let contract_id = env.register(BoundlessContract, ()); + let client = BoundlessContractClient::new(&env, &contract_id); + env.mock_all_auths(); + let creator = Address::generate(&env); + let project_id = String::from_str(&env, "test_project"); + let metadata_uri = String::from_str(&env, "https://example.com/metadata"); + let funding_target = 1000; + let milestone_count = 5; -// client.create_project( -// &project_id, -// &creator, -// &metadata_uri, -// &funding_target, -// &milestone_count, -// ); -// let voter = Address::generate(&env); -// let vote_value = 1; -// client.vote_project(&project_id, &voter, &vote_value); -// assert_eq!(client.get_vote(&project_id, &voter), vote_value); -// } + client.create_project( + &project_id, + &creator, + &metadata_uri, + &funding_target, + &milestone_count, + ); + let voter = Address::generate(&env); + let vote_value = 1; + client.vote_project(&project_id, &voter, &vote_value); + assert_eq!(client.get_vote(&project_id, &voter), vote_value); +} From 09480a76901d0648f3f0cc8d82e50fe56879af14 Mon Sep 17 00:00:00 2001 From: nagxsan Date: Thu, 1 May 2025 00:57:47 +0530 Subject: [PATCH 2/2] feat: add test snapshots for voting operation tests --- .../tests/voting/test_get_vote.1.json | 4 ++-- .../tests/voting/test_has_voted.1.json | 4 ++-- .../tests/voting/test_vote_project.1.json | 4 ++-- .../tests/voting/test_withdraw_vote.1.json | 18 ++++-------------- .../test_withdraw_vote_unauthorized.1.json | 4 ++-- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/contracts/boundless/test_snapshots/tests/voting/test_get_vote.1.json b/contracts/boundless/test_snapshots/tests/voting/test_get_vote.1.json index 9fa474f..0bdd2b7 100644 --- a/contracts/boundless/test_snapshots/tests/voting/test_get_vote.1.json +++ b/contracts/boundless/test_snapshots/tests/voting/test_get_vote.1.json @@ -123,7 +123,7 @@ "symbol": "funding_deadline" }, "val": { - "u64": 518400 + "u64": 0 } }, { @@ -211,7 +211,7 @@ "symbol": "status" }, "val": { - "u32": 1 + "u32": 2 } }, { diff --git a/contracts/boundless/test_snapshots/tests/voting/test_has_voted.1.json b/contracts/boundless/test_snapshots/tests/voting/test_has_voted.1.json index 9fa474f..0bdd2b7 100644 --- a/contracts/boundless/test_snapshots/tests/voting/test_has_voted.1.json +++ b/contracts/boundless/test_snapshots/tests/voting/test_has_voted.1.json @@ -123,7 +123,7 @@ "symbol": "funding_deadline" }, "val": { - "u64": 518400 + "u64": 0 } }, { @@ -211,7 +211,7 @@ "symbol": "status" }, "val": { - "u32": 1 + "u32": 2 } }, { diff --git a/contracts/boundless/test_snapshots/tests/voting/test_vote_project.1.json b/contracts/boundless/test_snapshots/tests/voting/test_vote_project.1.json index 9fa474f..0bdd2b7 100644 --- a/contracts/boundless/test_snapshots/tests/voting/test_vote_project.1.json +++ b/contracts/boundless/test_snapshots/tests/voting/test_vote_project.1.json @@ -123,7 +123,7 @@ "symbol": "funding_deadline" }, "val": { - "u64": 518400 + "u64": 0 } }, { @@ -211,7 +211,7 @@ "symbol": "status" }, "val": { - "u32": 1 + "u32": 2 } }, { diff --git a/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote.1.json b/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote.1.json index 9fa474f..a1f4a39 100644 --- a/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote.1.json +++ b/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote.1.json @@ -37,6 +37,7 @@ ] ], [], + [], [] ], "ledger": { @@ -123,7 +124,7 @@ "symbol": "funding_deadline" }, "val": { - "u64": 518400 + "u64": 0 } }, { @@ -211,7 +212,7 @@ "symbol": "status" }, "val": { - "u32": 1 + "u32": 2 } }, { @@ -235,18 +236,7 @@ "symbol": "votes" }, "val": { - "vec": [ - { - "vec": [ - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" - }, - { - "i32": 1 - } - ] - } - ] + "vec": [] } }, { diff --git a/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote_unauthorized.1.json b/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote_unauthorized.1.json index 9fa474f..0bdd2b7 100644 --- a/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote_unauthorized.1.json +++ b/contracts/boundless/test_snapshots/tests/voting/test_withdraw_vote_unauthorized.1.json @@ -123,7 +123,7 @@ "symbol": "funding_deadline" }, "val": { - "u64": 518400 + "u64": 0 } }, { @@ -211,7 +211,7 @@ "symbol": "status" }, "val": { - "u32": 1 + "u32": 2 } }, {