diff --git a/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/CPFTreasury.java b/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/CPFTreasury.java index 10665354..dc363dfe 100644 --- a/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/CPFTreasury.java +++ b/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/CPFTreasury.java @@ -39,6 +39,8 @@ public class CPFTreasury extends SetterGetter implements CPFTreasuryInterface { private final VarDB swapFlag = Context.newVarDB(SWAP_FLAG, Boolean.class); private final VarDB swapLimitAmount = Context.newVarDB(SWAP_LIMIT_AMOUNT, BigInteger.class); + private final VarDB councilFlag = Context.newVarDB(COUNCIL_FLAG, Boolean.class); + public CPFTreasury(@Optional Address cpsScore) { if (treasuryFund.get() == null) { treasuryFund.set(BigInteger.valueOf(1000000).multiply(EXA)); @@ -499,6 +501,27 @@ public void migrateOldHashToNewHash(String oldHash, String newHash){ proposalBudgets.set(newHash,totalBudget); } + @External + public void toggleCouncilFlag() { + validateAdmins(); + councilFlag.set(!councilFlag.getOrDefault(false)); + } + + @External(readonly = true) + public boolean getCouncilFlag() { + return councilFlag.getOrDefault(false); + } + + // @External + // public List
setCouncilManagers() { + // validateAdmins(); + + // } + + // @External + // public List
getCouncilManagers() { + + // } //EventLogs @Override diff --git a/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/Constants.java b/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/Constants.java index a440ebdb..49c3c30f 100644 --- a/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/Constants.java +++ b/CPFTreasury/src/main/java/community/icon/cps/score/cpftreasury/Constants.java @@ -49,6 +49,8 @@ public class Constants { public static final String STATE = "state"; public static final Address SYSTEM_ADDRESS = Address.fromString("cx0000000000000000000000000000000000000000"); + public static final String COUNCIL_FLAG = "council_flag"; + public static final int sICXICXPoolID = 1; public static final int sICXBNUSDPoolID = 2; diff --git a/CPSCore/src/main/java/community/icon/cps/score/cpscore/CPSCore.java b/CPSCore/src/main/java/community/icon/cps/score/cpscore/CPSCore.java index 482c270f..91a4ef9d 100644 --- a/CPSCore/src/main/java/community/icon/cps/score/cpscore/CPSCore.java +++ b/CPSCore/src/main/java/community/icon/cps/score/cpscore/CPSCore.java @@ -66,28 +66,28 @@ public CPSCore(@Optional BigInteger bondValue, @Optional BigInteger applicationP // Fix for ICON Dashboard Proposal not being able to submit progress report being rejected - String progressReport = "bafybeic4jhoowlgeyewjqkcpgbm3fs7mgtxzhqd5kbguihoehm4cwwwzcq"; - String proposalKey = "bafybeidoxysex3jloigzi4pvdeqgy35a4nykd46w66pobxrkephpawyjoi"; + // String progressReport = "bafybeic4jhoowlgeyewjqkcpgbm3fs7mgtxzhqd5kbguihoehm4cwwwzcq"; + // String proposalKey = "bafybeidoxysex3jloigzi4pvdeqgy35a4nykd46w66pobxrkephpawyjoi"; - String proposalPrefix = proposalPrefix(proposalKey); - String progressReportPrefix = progressReportPrefix(progressReport); + // String proposalPrefix = proposalPrefix(proposalKey); + // String progressReportPrefix = progressReportPrefix(progressReport); - ProposalDataDb.submitProgressReport.at(proposalPrefix).set(Boolean.FALSE); + // ProposalDataDb.submitProgressReport.at(proposalPrefix).set(Boolean.FALSE); - ProgressReportDataDb.status.at(progressReportPrefix).set(PROGRESS_REPORT_REJECTED); - ProgressReportDataDb.timestamp.at(progressReportPrefix).set(BigInteger.valueOf(1706244342109937L)); - Status status = new Status(); - status.progressReportStatus.get(PROGRESS_REPORT_REJECTED).add(progressReport); + // ProgressReportDataDb.status.at(progressReportPrefix).set(PROGRESS_REPORT_REJECTED); + // ProgressReportDataDb.timestamp.at(progressReportPrefix).set(BigInteger.valueOf(1706244342109937L)); + // Status status = new Status(); + // status.progressReportStatus.get(PROGRESS_REPORT_REJECTED).add(progressReport); // End of fix // Migrate sponsor bond from Techiast- to Techiast - Address oldAddr = Address.fromString("hxdc4b3fb5b47d6c14c7f9a0bac8eea9f3f48d3288"); - Address address = Address.fromString("hxfe0256b41f1db0186f9e6cbebf8c71cf006d5d17"); - DictDB userAmounts = sponsorBondReturn.at(oldAddr.toString()); + // Address oldAddr = Address.fromString("hxdc4b3fb5b47d6c14c7f9a0bac8eea9f3f48d3288"); + // Address address = Address.fromString("hxfe0256b41f1db0186f9e6cbebf8c71cf006d5d17"); + // DictDB userAmounts = sponsorBondReturn.at(oldAddr.toString()); - BigInteger bnUSDAmount = userAmounts.getOrDefault(bnUSD, BigInteger.ZERO); - sponsorBondReturn.at(address.toString()).set(bnUSD, bnUSDAmount); + // BigInteger bnUSDAmount = userAmounts.getOrDefault(bnUSD, BigInteger.ZERO); + // sponsorBondReturn.at(address.toString()).set(bnUSD, bnUSDAmount); } @@ -723,7 +723,15 @@ public void voteProposal(String ipfsKey, String vote, String voteReason, @Option TAG + ": Proposals can be voted only on Voting Period."); Address caller = Context.getCaller(); PReps pReps = new PReps(); - Context.require(ArrayDBUtils.containsInArrayDb(caller, pReps.validPreps), + + List
callLocation; + if (getCouncilFlag()) { + callLocation = getCouncilManagers(); + } else { + callLocation = ArrayDBUtils.arrayDBtoList(pReps.validPreps); + } + + Context.require(ArrayDBUtils.containsInList(caller, callLocation), TAG + ": Voting can only be done by registered P-Reps."); Context.require(List.of(APPROVE, REJECT, ABSTAIN).contains(vote), TAG + ": Vote should be either _approve, _reject or _abstain"); @@ -976,9 +984,17 @@ public void voteProgressReport(String reportKey, String voteReason, MilestoneVot TAG + ": Progress Reports can be voted only on Voting Period."); Address caller = Context.getCaller(); PReps pReps = new PReps(); + + List
callLocation = new ArrayList<>(); + if (getCouncilFlag()) { + callLocation = getCouncilManagers(); + } else { + callLocation = ArrayDBUtils.arrayDBtoList(pReps.validPreps); + } + Context.require(!ArrayDBUtils.containsInArrayDb(caller, blockAddresses), TAG + ": You are blocked from CPS."); - Context.require(ArrayDBUtils.containsInArrayDb(caller, pReps.validPreps), + Context.require(ArrayDBUtils.containsInList(caller, callLocation), TAG + ": Voting can only be done by registered P-Reps."); String progressReportPrefix = progressReportPrefix(reportKey); ArrayDB submittedMilestones = milestoneSubmitted.at(progressReportPrefix);// CAN TAKE FROM READONLY METHOD @@ -2656,6 +2672,35 @@ public Map getProposalsHistory(@Optional int startIndex) { return milestoneIdList; } + @External(readonly = true) + public Boolean hasTwoThirdsMajority(String key, boolean isMilestone) { + //can also use if else if this doesnt work as intended + + //error: milestonedb doesnt have total votes or voters + + Map milestoneDbData = MilestoneDb.getDataFromMilestoneDB(key); + BigInteger totalVotes = isMilestone ? (BigInteger)milestoneDbData.get(TOTAL_VOTES) : ProgressReportDataDb.totalVotes.at(key).getOrDefault(BigInteger.ZERO); + int totalVoters = isMilestone ? (Integer)milestoneDbData.get(TOTAL_VOTERS) : ProgressReportDataDb.totalVoters.at(key).getOrDefault(0); + + BigInteger approveVotes = isMilestone ? MilestoneDb.approvedVotes.at(key).getOrDefault(BigInteger.ZERO) : ProgressReportDataDb.approvedVotes.at(key).getOrDefault(BigInteger.ZERO); + int approveVoters = isMilestone ? MilestoneDb.approveVoters.at(key).size() : ProgressReportDataDb.approveVoters.at(key).size(); + + //need to give vote weights = 100 (arbitrary) + boolean voteWeightCheck = approveVotes.multiply(BigInteger.valueOf(3)).compareTo(totalVotes.multiply(BigInteger.valueOf(2))) >= 0; + boolean voterCountCheck = approveVoters * 3 >= totalVoters * 2; + + return voteWeightCheck && voterCountCheck; + } + + public List
getCouncilManagers() { + return callScore(List.class, getCpfTreasuryScore(), "getCouncilManagers"); + } + + + public boolean getCouncilFlag() { + return callScore(boolean.class, getCpfTreasuryScore(), "getCouncilFlag"); + } + // =====================================TEMPORARY MIGRATIONS METHODS=============================================== @External @@ -2864,5 +2909,10 @@ public void onlyCPFTreasury() { Context.require(Context.getCaller().equals(getCpfTreasuryScore()), TAG + ": Only CPF treasury can call this method"); } + public void callScore(Class eq, Class eq2, String eq3, String eq4) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'callScore'"); + } + // =====================================Checkers=============================================== } \ No newline at end of file diff --git a/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/MilestoneDb.java b/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/MilestoneDb.java index 4fe94274..b6698f87 100644 --- a/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/MilestoneDb.java +++ b/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/MilestoneDb.java @@ -23,6 +23,8 @@ public class MilestoneDb { public static final BranchDB>> votersListIndices = Context.newBranchDB(VOTERS_LIST_INDEXES, Integer.class); + public static final BranchDB> majorityFlag = Context.newBranchDB(MAJORITY_FLAG, Boolean.class); + public static void addDataToMilestoneDb(CPSCoreInterface.MilestonesAttributes milestoneData, String prefix) { id.at(prefix).set(milestoneData.id); approvedVotes.at(prefix).set(BigInteger.ZERO); @@ -30,6 +32,8 @@ public static void addDataToMilestoneDb(CPSCoreInterface.MilestonesAttributes mi completionPeriod.at(prefix).set(milestoneData.completionPeriod); budget.at(prefix).set(milestoneData.budget); + majorityFlag.at(prefix).set(false); + } public static Map getDataFromMilestoneDB(String prefix) { @@ -46,7 +50,10 @@ public static Map getDataFromMilestoneDB(String prefix) { Map.entry(TOTAL_VOTERS, ProgressReportDataDb.totalVoters.at(progressReportPrefix(reportHash)).getOrDefault(0)), Map.entry(APPROVE_VOTERS, approveVoters.at(prefix).size()), Map.entry(REJECT_VOTERS, rejectVoters.at(prefix).size()), - Map.entry(EXTENSION_FLAG, extensionFlag.at(prefix).getOrDefault(false))); + Map.entry(EXTENSION_FLAG, extensionFlag.at(prefix).getOrDefault(false)), + + Map.entry(MAJORITY_FLAG, majorityFlag.at(prefix).getOrDefault(false))); + } public static String progressReportPrefix(String progressHash) { diff --git a/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/ProposalDataDb.java b/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/ProposalDataDb.java index c0fa6cbe..e866b9cf 100644 --- a/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/ProposalDataDb.java +++ b/CPSCore/src/main/java/community/icon/cps/score/cpscore/db/ProposalDataDb.java @@ -5,6 +5,8 @@ import java.math.BigInteger; import java.util.Map; +import community.icon.cps.score.lib.interfaces.CPSCoreInterface.ProposalAttributes; + import static community.icon.cps.score.cpscore.utils.ArrayDBUtils.recordTxHash; import static community.icon.cps.score.cpscore.utils.Constants.*; import static community.icon.cps.score.lib.interfaces.CPSCoreInterface.ProposalAttributes; @@ -46,6 +48,9 @@ public class ProposalDataDb { public static final BranchDB> proposalPeriod = Context.newBranchDB(PROPOSAL_PERIOD, Integer.class); public static final BranchDB> milestoneIds = Context.newBranchDB("milestoneIds", Integer.class); + public static final BranchDB> majorityFlag = Context.newBranchDB(MAJORITY_FLAG, Boolean.class); + public static final BranchDB> councilFlag = Context.newBranchDB(COUNCIL_FLAG, Boolean.class); + public static void addDataToProposalDB(ProposalAttributes proposalData, String prefix) { ipfsHash.at(prefix).set(proposalData.ipfs_hash); projectTitle.at(prefix).set(proposalData.project_title); @@ -69,6 +74,9 @@ public static void addDataToProposalDB(ProposalAttributes proposalData, String p milestoneCount.at(prefix).set(proposalData.milestoneCount); isMilestone.at(prefix).set(true); + majorityFlag.at(prefix).set(false); + councilFlag.at(prefix).set(false); + } public static void updatePercentageCompleted(String prefix, int percentage) { @@ -96,7 +104,11 @@ public static Map getDataFromProposalDB(String prefix) { Map.entry(IS_MILESTONE,isMilestone.at(prefix).getOrDefault(false)), Map.entry(PERCENTAGE_COMPLETED,percentageCompleted.at(prefix).getOrDefault(0)), Map.entry(SUBMIT_PROGRESS_REPORT, submitProgressReport.at(prefix).getOrDefault(false)), - Map.entry(PROPOSAL_PERIOD,proposalPeriod.at(prefix).getOrDefault(0))); + Map.entry(PROPOSAL_PERIOD,proposalPeriod.at(prefix).getOrDefault(0)), + + Map.entry(MAJORITY_FLAG, majorityFlag.at(prefix).getOrDefault(false)), + Map.entry(COUNCIL_FLAG, councilFlag.at(prefix).getOrDefault(false))); + } public static int getMilestoneCount(String prefix) { diff --git a/CPSCore/src/main/java/community/icon/cps/score/cpscore/utils/Constants.java b/CPSCore/src/main/java/community/icon/cps/score/cpscore/utils/Constants.java index 6643dc30..a18e3aff 100644 --- a/CPSCore/src/main/java/community/icon/cps/score/cpscore/utils/Constants.java +++ b/CPSCore/src/main/java/community/icon/cps/score/cpscore/utils/Constants.java @@ -209,6 +209,10 @@ public class Constants { public static final Integer MILESTONE_REPORT_APPROVED = 3; public static final Integer MILESTONE_REPORT_NOT_COMPLETED = 4; + // new flags + + public static final String MAJORITY_FLAG = "majority_flag"; + public static final String COUNCIL_FLAG = "council_flag"; public static final BigInteger TOTAL_PERIOD = BigInteger.valueOf(30); } \ No newline at end of file diff --git a/CPSCore/src/test/java/community/icon/cps/score/cpscore/CPSScoreTest.java b/CPSCore/src/test/java/community/icon/cps/score/cpscore/CPSScoreTest.java index b0ac027a..a728836d 100644 --- a/CPSCore/src/test/java/community/icon/cps/score/cpscore/CPSScoreTest.java +++ b/CPSCore/src/test/java/community/icon/cps/score/cpscore/CPSScoreTest.java @@ -5,12 +5,18 @@ import com.iconloop.score.test.Score; import com.iconloop.score.test.ServiceManager; import com.iconloop.score.test.TestBase; + +import community.icon.cps.score.cpscore.db.MilestoneDb; import community.icon.cps.score.cpscore.utils.Constants; import community.icon.cps.score.lib.interfaces.CPSCoreInterface; +import community.icon.cps.score.lib.interfaces.CPSCoreInterface.ProgressReportAttributes; +import community.icon.cps.score.lib.interfaces.CPSCoreInterface.ProposalAttributes; + import org.junit.jupiter.api.*; import org.junit.jupiter.api.function.Executable; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import score.Address; import score.ArrayDB; @@ -20,12 +26,17 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import scorex.util.HashMap; + import static community.icon.cps.score.cpscore.utils.Constants.*; import static community.icon.cps.score.lib.interfaces.CPSCoreInterface.ProgressReportAttributes; import static community.icon.cps.score.lib.interfaces.CPSCoreInterface.ProposalAttributes; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -499,6 +510,9 @@ void sponsorVote() { void submitAndSponsorVote() { submitProposalMethod(); + + doReturn(false).when(scoreSpy).getCouncilFlag(); + contextMock.when(caller()).thenReturn(bnUSDScore); JsonObject sponsorVoteParams = new JsonObject(); sponsorVoteParams.add("method", "sponsorVote"); @@ -604,6 +618,14 @@ void voteProposal2() { } void voteProposalMethod() { + + // doReturn(List.of(cpfTreasury)).when(scoreSpy).callScore(cpfTreasury, "getCouncilManagers"); + + doReturn(List.of(cpfTreasury)).when(scoreSpy).callScore(List.class, cpfTreasury, "getCouncilManagers"); + doReturn(false).when(scoreSpy).getCouncilFlag(); + + //mockito void method cannot be stubbed. + submitAndSponsorVote(); contextMock.when(caller()).thenReturn(owner.getAddress()); updateNextBlock(); @@ -720,6 +742,9 @@ void voteMultipleProposals() { String[] proposal = new String[]{"Proposal 0", "Proposal 1", "Proposal 2", "Proposal 3", "Proposal 4", "Proposal 5", "Proposal 6", "Proposal 7", "Proposal 8", "Proposal 9"}; + + doReturn(false).when(scoreSpy).getCouncilFlag(); + for (int i = 0; i < 10; i++) { contextMock.when(caller()).thenReturn(owner.getAddress()); cpsScore.invoke(owner, "voteProposal", "Proposal " + i, APPROVE, "reason", false); @@ -768,6 +793,9 @@ void voteMultipleProposals() { void voteProposalMethodReject() { submitAndSponsorVote(); + + doReturn(false).when(scoreSpy).getCouncilFlag(); + contextMock.when(caller()).thenReturn(owner.getAddress()); updateNextBlock(); doReturn(BigInteger.valueOf(15)).when(scoreSpy).getApplicationPeriod(); @@ -2046,6 +2074,130 @@ void submitProgressReportBeforeDeadline() { } + //begin +// @Test +// void testhasTwoThirdsMajority(){ +// String key="test"; + +// doReturn(BigInteger.valueOf(15)).when(scoreSpy).getSponsorBondPercentage(); +// registerPrepsMethod(); + + +// //need attributes? + +// //set flag to true for mock test +// //doReturn(true).when(cpsScore).call(Boolean.class, ((Object) cpfTreasury).get(), "getCouncilFlag"); +// // boolean councilFlag = true; +// doReturn(true).when(scoreSpy).getCouncilFlag(); + +// // sponsor address sponsor voter valid prep ma exist huna paryo +// //votes 3 wota weight 100= constant 100x10=1000 + +// setMilestoneVotes(key, BigInteger.valueOf(1000), 10); +// setApprovedVotes(key, BigInteger.valueOf(700), 7,true); + +// Boolean result = call(Boolean.class, cpsScore.getAddress(), "hasTwoThirdsMajority", key); +// assertTrue(result, "Should meet 2/3 majority"); + +// contextMock.when(caller()).thenReturn(testingAccount.getAddress()); +// setMilestoneVotes(key, BigInteger.valueOf(1000), 10); +// setApprovedVotes(key, BigInteger.valueOf(300), 3, true); + +// result = call(Boolean.class, cpsScore.getAddress(), "hasTwoThirdsMajority", key); +// assertFalse(result, "Should meet 2/3 majority"); + +// //flag to false +// // doReturn(false).when(cpsScore).call(Boolean.class, ((Object) cpfTreasury).get(), "getCouncilFlag"); +// // councilFlag = false; +// doReturn(false).when(scoreSpy).getCouncilFlag(); + +// contextMock.when(caller()).thenReturn(testingAccount1.getAddress()); +// setMilestoneVotes(key, BigInteger.valueOf(1000), 10); +// setApprovedVotes(key, BigInteger.valueOf(700), 7, true); + +// result = call(Boolean.class, cpsScore.getAddress(), "hasTwoThirdsMajority", key); +// assertTrue(result, "Should meet 2/3 majority"); + +// contextMock.when(caller()).thenReturn(testingAccount.getAddress()); +// setMilestoneVotes(key, BigInteger.valueOf(1000), 10); +// setApprovedVotes(key, BigInteger.valueOf(300), 3, true); + +// result = call(Boolean.class, cpsScore.getAddress(), "hasTwoThirdsMajority", key); +// assertFalse(result, "Should meet 2/3 majority"); + +// } + +// private Boolean call(Class returnType, Address address, String method, String key) { +// return Context.call(returnType, address, method, key); +// } + +// void setMilestoneVotes(String key, BigInteger totalVotes, int totalVoters) { +// cpsScore.invoke(owner, "setTotalVotes", key, totalVotes, totalVoters, true); +// } + +// void setProposalVotes(String key, BigInteger totalVotes, int totalVoters) { +// cpsScore.invoke(owner, "setTotalVotes", key, totalVotes, totalVoters, false); +// } + +// void setApprovedVotes(String key, BigInteger approvedVotes, int approveVoters, boolean isMilestone) { +// cpsScore.invoke(owner, "setApprovedVotes", key, approvedVotes, approveVoters, isMilestone); +// } + + public class VotingTest { + + private void commonSetup() { + + // Mock methods and setup initial conditions + doReturn(BigInteger.valueOf(3)).when(scoreSpy).callScore(eq(BigInteger.class), any(), eq("getTotalVotes"), any()); + doReturn(BigInteger.ZERO).when(scoreSpy).callScore(eq(BigInteger.class), any(), eq("getApprovedVotes"), any()); + doReturn(3).when(scoreSpy).callScore(eq(Integer.class), any(), eq("getTotalVoters"), any()); + } + + @Test + public void testVotingProcess() { + // Setup required for this specific test + commonSetup(); + + // Simulate the first voter + CPSCoreInterface.MilestoneVoteAttributes firstVote = new CPSCoreInterface.MilestoneVoteAttributes(); + firstVote.vote = APPROVE; + firstVote.id = 1; + + // Invoke the voting method for the first voter + cpsScore.invoke(owner, "voteProposal", "Proposal 1", firstVote, false); + + // Check if the majority is met after the first vote + boolean isMajorityAfterFirstVote = (Boolean)cpsScore.call("hasTwoThirdsMajority","Proposal 1", true); + assertFalse(isMajorityAfterFirstVote); // Expecting false + + // Simulate the second voter + CPSCoreInterface.MilestoneVoteAttributes secondVote = new CPSCoreInterface.MilestoneVoteAttributes(); + secondVote.vote = APPROVE; + secondVote.id = 2; + + // Invoke the voting method for the second voter + cpsScore.invoke(owner, "voteProposal", "Proposal 1", secondVote, false); + + // Check if the majority is met after the second vote + boolean isMajorityAfterSecondVote = (Boolean)cpsScore.call("hasTwoThirdsMajority","Proposal 1", true); + assertTrue(isMajorityAfterSecondVote); // Expecting true + + // Simulate the third voter + CPSCoreInterface.MilestoneVoteAttributes thirdVote = new CPSCoreInterface.MilestoneVoteAttributes(); + thirdVote.vote = APPROVE; + thirdVote.id = 3; + + // Invoke the voting method for the third voter + cpsScore.invoke(owner, "voteProposal", "Proposal 1", thirdVote, false); + + // Check if the majority is met after the third vote + Boolean isMajorityAfterThirdVote = (Boolean)cpsScore.call("hasTwoThirdsMajority","Proposal 1", true); + assertTrue(isMajorityAfterThirdVote); // Expecting true + } + + +} + //end @Test void setSwapCount() {