From 3f5de4ac4c7b8f46284218411395e256ff734d9e Mon Sep 17 00:00:00 2001 From: karonka Date: Fri, 1 May 2020 12:23:33 +0300 Subject: [PATCH] Historical voter confidence (#8) --- contracts/KingAutomaton.sol | 30 +++-- contracts/Proposals.sol | 54 +++++++++ migrations/2_deploy_contracts.js | 3 +- test/KingAutomatonDEX.js | 2 +- test/ProposalsTest.js | 186 ++++++++++++++++++++++++++---- test/TestKingAutomatonDebug.sol | 2 +- test/TestKingAutomatonNoDebug.sol | 2 +- 7 files changed, 247 insertions(+), 32 deletions(-) diff --git a/contracts/KingAutomaton.sol b/contracts/KingAutomaton.sol index eda7314..b1713d1 100644 --- a/contracts/KingAutomaton.sol +++ b/contracts/KingAutomaton.sol @@ -25,20 +25,22 @@ contract KingAutomaton is KingOfTheHill { constructor(uint256 _numSlots, uint256 _minDifficultyBits, uint256 _predefinedMask, uint256 _initialDailySupply, int256 _approvalPct, int256 _contestPct, uint256 _treasuryLimitPct, uint256 _proposalsInitialPeriod, uint256 _proposalsContestPeriod, uint256 _proposalsMinPeriodLen, - uint256 _timeUnitInSeconds) public { + uint256 _numHistoryPeriods, uint256 _timeUnitInSeconds) public { + require(_approvalPct > _contestPct, "Approval% should be > contest%!"); numSlots = _numSlots; - initMining(_numSlots, _minDifficultyBits, _predefinedMask, _initialDailySupply); - initNames(); - - require(_approvalPct > _contestPct, "Approval percentage must be bigger than contest percentage!"); proposalsData.approvalPercentage = _approvalPct; proposalsData.contestPercentage = _contestPct; proposalsData.treasuryLimitPercentage = _treasuryLimitPct; proposalsData.ballotBoxIDs = 99; // Ensure special addresses are not already used + proposalsData.numHistoryPeriods = _numHistoryPeriods; + proposalsData.minHistoryPeriod = _timeUnitInSeconds; proposalsInitialPeriod = _proposalsInitialPeriod * _timeUnitInSeconds; proposalsContestPeriod = _proposalsContestPeriod * _timeUnitInSeconds; proposalsMinPeriodLen = _proposalsMinPeriodLen; timeUnitInSeconds = _timeUnitInSeconds; + + initMining(_numSlots, _minDifficultyBits, _predefinedMask, _initialDailySupply); + initNames(); // Check if we're on a testnet (We will not using predefined mask when going live) if (_predefinedMask != 0) { // If so, fund the owner for debugging purposes. @@ -170,9 +172,11 @@ contract KingAutomaton is KingOfTheHill { initialPeriod = p.initialPeriod; contestPeriod = p.contestPeriod; } + function getProposalData(uint256 _id) public view returns (uint256 remainingPeriods, uint256 nextPaymentDate, - Proposals.ProposalState state, uint256 initialEndDate, uint256 contestEndDate) { + Proposals.ProposalState state, uint256 initialEndDate, uint256 contestEndDate, uint256[] memory votingHistory, + uint256 historyStartIdx) { Proposals.Proposal memory p = proposalsData.proposals[_id]; remainingPeriods = p.remainingPeriods; @@ -180,6 +184,15 @@ contract KingAutomaton is KingOfTheHill { state = p.state; initialEndDate = p.initialEndDate; contestEndDate = p.contestEndDate; + + // TODO(Kari): Decide what to do when history is too long - give only first n words starting from current word? + uint256 numWords = (proposalsData.numHistoryPeriods + 31) / 32; + votingHistory = new uint256[](numWords); + Proposals.ProposalVotingHistory storage h = proposalsData.proposals[_id].history; + for(uint256 i = 0; i < numWords; ++i) { + votingHistory[i] = h.words[i]; + } + historyStartIdx = h.front; } function createBallotBox(uint256 _choices) public returns (uint256) { @@ -207,6 +220,8 @@ contract KingAutomaton is KingOfTheHill { p.initialPeriod = proposalsInitialPeriod; p.contestPeriod = proposalsContestPeriod; + p.history.front = 1; + transferInternal(treasuryAddress, address(_id), num_periods * budget_per_period); } @@ -228,6 +243,7 @@ contract KingAutomaton is KingOfTheHill { function castVote(uint256 _id, uint256 _slot, uint8 _choice) public slotOwner(_slot) { updateProposalState(_id); proposalsData.castVote(_id, _slot, _choice); + proposalsData.updateProposalHistory(_id); } function getVote(uint256 _id, uint256 _slot) public view returns (uint) { @@ -327,7 +343,7 @@ contract KingAutomaton is KingOfTheHill { // Setup predefined mask, useful for testing purposes. setMask(predefinedMask); } - rewardPerSlotPerSecond = (1 ether * initialDailySupply) / 1 days / nSlots; + rewardPerSlotPerSecond = (1 ether * initialDailySupply) / timeUnitInSeconds / nSlots; } function slotAcquired(uint256 id) internal override { diff --git a/contracts/Proposals.sol b/contracts/Proposals.sol index 345566e..386b080 100644 --- a/contracts/Proposals.sol +++ b/contracts/Proposals.sol @@ -42,6 +42,12 @@ library Proposals { mapping (uint256 => uint256) payGas2; } + struct ProposalVotingHistory { + uint256 lastTime; + uint256 front; + mapping (uint256 => uint256) words; + } + struct Proposal { address payable contributor; string title; @@ -59,6 +65,8 @@ library Proposals { uint256 contestEndDate; uint256 initialPeriod; uint256 contestPeriod; + + ProposalVotingHistory history; } struct Data { @@ -66,6 +74,8 @@ library Proposals { int256 contestPercentage; uint256 treasuryLimitPercentage; uint256 ballotBoxIDs; + uint256 numHistoryPeriods; + uint256 minHistoryPeriod; mapping (uint256 => BallotBox) ballotBoxes; mapping (uint256 => Proposal) proposals; @@ -122,6 +132,47 @@ library Proposals { return (yes - no) * 100 / int256(b.numSlots); } + function updateProposalHistory(Data storage self, uint256 id) public { + Proposal storage p = self.proposals[id]; + if (p.state == ProposalState.Uninitialized) { + return; + } + ProposalVotingHistory storage h = p.history; + + // Time passed is less than a period + if (now - h.lastTime < self.minHistoryPeriod) { + return; + } + uint256 periods = self.numHistoryPeriods; + uint256 front = h.front; + uint256 rear = (front + periods - 1) % periods; + uint256 wordIdx = front / 32; + uint256 offset = (front % 32) * 8; + uint256 mask = 255 << offset; + uint256 word = h.words[wordIdx]; + + uint256 oldVal = (word & mask) >> offset; + uint256 newVal = uint256(calcVoteDifference(self, id) + 100); + if (oldVal == newVal) { + return; + } + + if (wordIdx != (rear / 32)) { + wordIdx = rear / 32; + word = h.words[wordIdx]; + } + + offset = (rear % 32) * 8; + mask = 255 << offset; + + word &= (mask ^ UINT256_MAX); + word |= newVal << offset; + + h.words[wordIdx] = word; + h.front = rear; + h.lastTime = now; + } + function castVote(Data storage self, uint256 _id, uint256 _slot, uint8 _choice) public validBallotBoxID(self, _id) { BallotBox storage box = self.ballotBoxes[_id]; uint256 numChoices = box.numChoices; @@ -177,6 +228,9 @@ library Proposals { ) public validBallotBoxID(self, _id) returns (bool _return_to_treasury) { Proposal storage p = self.proposals[_id]; ProposalState p_state = p.state; + if (p_state == ProposalState.Uninitialized) { + return false; + } uint256 _initialEndDate = p.initialEndDate; if (p_state == ProposalState.Started) { BallotBox storage b = self.ballotBoxes[_id]; diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index fcc9f00..566d4ae 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -24,9 +24,10 @@ module.exports = function(deployer) { _proposalsInitialPeriod = 7; _proposalsContestPeriod = 7; _proposalsMinPeriodLen = 3; + _proposalsNumHistoryPeriods = 128; _timeUnitInSeconds = 24 * 60 * 60; // 1 day deployer.deploy(KingAutomaton, _numSlots, _minDifficultyBits, _predefinedMask, _initialDailySupply, _approvalPct, _contestPct, _treasuryLimitPct, _proposalsInitialPeriod, _proposalsContestPeriod, _proposalsMinPeriodLen, - _timeUnitInSeconds); + _proposalsNumHistoryPeriods, _timeUnitInSeconds); }; diff --git a/test/KingAutomatonDEX.js b/test/KingAutomatonDEX.js index 777fce1..7eedcc4 100644 --- a/test/KingAutomatonDEX.js +++ b/test/KingAutomatonDEX.js @@ -18,7 +18,7 @@ describe('TestKingAutomatonDEX', async (accounts) => { beforeEach(async () => { accounts = await web3.eth.getAccounts(); mainAcct = accounts[0]; - koh = await KingAutomaton.new(4, 16, "0x010000", "406080000", 10, -10, 2, 7, 7, 3, 24 * 60 * 60); + koh = await KingAutomaton.new(4, 16, "0x010000", "406080000", 10, -10, 2, 7, 7, 3, 128, 24 * 60 * 60); minOrderAUTO = await koh.minOrderAUTO(); minOrderETH = await koh.minOrderETH(); DEXAddress = "0x0000000000000000000000000000000000000002"; diff --git a/test/ProposalsTest.js b/test/ProposalsTest.js index 2cda6fd..33f6de5 100644 --- a/test/ProposalsTest.js +++ b/test/ProposalsTest.js @@ -26,11 +26,11 @@ const PROPOSAL_STATE_COMPLETED = 5; const PROPOSALS_INITIAL_PERIOD = 7; const PROPOSALS_CONTEST_PERIOD = 7; const PROPOSALS_MIN_PERIOD_LEN = 3; +const NUM_HISTORY_PERIODS = 128; const TIME_UNIT_IN_SECONDS = 120; // 2 minutes describe('TestKingAutomatonProposals 4 slots', async() => { const KingAutomaton = artifacts.require("KingAutomaton"); - const Proposals = artifacts.require("Proposals"); beforeEach(async() => { accounts = await web3.eth.getAccounts(); @@ -39,7 +39,8 @@ describe('TestKingAutomatonProposals 4 slots', async() => { // Deploy contract and create proposal. koh = await KingAutomaton.new(slots, 16, "0x010000", "406080000", 10, -10, 2, - PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, TIME_UNIT_IN_SECONDS); + PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, + NUM_HISTORY_PERIODS, TIME_UNIT_IN_SECONDS); id = await koh.createProposal.call(account, "", "", "0x", 30, 3, 20); await koh.createProposal(account, "", "", "0x", 30, 3, 20); @@ -53,7 +54,7 @@ describe('TestKingAutomatonProposals 4 slots', async() => { assert.exists(koh.address, "Contract wasn't deployed!"); let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_PREPAYING, "Ballot box state is not PrepayingGas!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_STARTED, "Proposal state is not Started!"); let proposal_data = await koh.proposalsData(); assert.equal(proposal_data.approvalPercentage, 10, "Approval % in incorrect!"); @@ -108,7 +109,7 @@ describe('TestKingAutomatonProposals 4 slots', async() => { // Check if states are correct let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_STARTED, "Proposal state is not Started!"); // Cast negative vote await koh.castVote(id, 0, VOTE_NO); @@ -116,13 +117,13 @@ describe('TestKingAutomatonProposals 4 slots', async() => { // States shouldn't change during initial time ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_STARTED, "Proposal state is not Started!"); increaseTime(proposals_initial_period); await koh.updateProposalState(id); ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_INACTIVE, "Ballot box state is not Inactive!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_REJECTED, "Proposal state is not Rejected!"); let vote_difference = await koh.calcVoteDifference(id); assert.equal(vote_difference, -25, "Vote difference is incorrect!"); @@ -137,7 +138,7 @@ describe('TestKingAutomatonProposals 4 slots', async() => { await koh.updateProposalState(id); let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_INACTIVE, "Ballot box state is not Inactive!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_REJECTED, "Proposal state is not Rejected!"); let vote_difference = await koh.calcVoteDifference(id); assert.equal(vote_difference, 0, "Vote difference is incorrect!"); @@ -154,7 +155,7 @@ describe('TestKingAutomatonProposals 4 slots', async() => { await koh.updateProposalState(id); let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_INACTIVE, "Ballot box state is not Inactive!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_REJECTED, "Proposal state is not Rejected!"); }); @@ -169,7 +170,6 @@ describe('TestKingAutomatonProposals 4 slots', async() => { assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_ACCEPTED, "Proposal state is not Accepted!"); - // Vote negative to enter contested state await koh.castVotesForRejection(id); await koh.updateProposalState(id); @@ -230,7 +230,6 @@ describe('TestKingAutomatonProposals 4 slots', async() => { describe('TestKingAutomatonProposals 256 slots', async() => { const KingAutomaton = artifacts.require("KingAutomaton"); - const Proposals = artifacts.require("Proposals"); beforeEach(async() => { accounts = await web3.eth.getAccounts(); @@ -239,7 +238,8 @@ describe('TestKingAutomatonProposals 256 slots', async() => { // Deploy contract and create proposal. koh = await KingAutomaton.new(slots, 16, "0x010000", "406080000", 10, -10, 2, - PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, TIME_UNIT_IN_SECONDS); + PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, + NUM_HISTORY_PERIODS, TIME_UNIT_IN_SECONDS); id = await koh.createProposal.call(account, "", "", "0x", 30, 3, 20); await koh.createProposal(account, "", "", "0x", 30, 3, 20); @@ -315,7 +315,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_INACTIVE, "Ballot box state is not Inactive!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_REJECTED, "Proposal state is not Rejected!"); }); @@ -361,7 +361,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_ACCEPTED, "Proposal state is not Accepted!"); // Vote negative to enter contested state @@ -369,7 +369,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_CONTESTED, "Proposal state is not Contested!"); @@ -378,7 +378,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_INACTIVE, "Ballot box state is not Inactive!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_REJECTED, "Proposal state is not Rejected!"); }); @@ -399,7 +399,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); let ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - let proposal = await koh.getProposalData(id) + let proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_ACCEPTED, "Proposal state is not Accepted!"); // Vote negative to enter contested state @@ -407,7 +407,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_CONTESTED, "Proposal state is not Contested!"); // Vote positive @@ -416,7 +416,7 @@ describe('TestKingAutomatonProposals 256 slots', async() => { // States shouldn't change until the end of contest ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_CONTESTED, "Proposal state is not Contested!"); vote_difference = await koh.calcVoteDifference(id); @@ -425,14 +425,13 @@ describe('TestKingAutomatonProposals 256 slots', async() => { await koh.updateProposalState(id); ballot = await koh.getBallotBox(id); assert.equal(ballot.state, BALLOT_STATE_ACTIVE, "Ballot box state is not Active!"); - proposal = await koh.getProposalData(id) + proposal = await koh.getProposalData(id); assert.equal(proposal.state, PROPOSAL_STATE_ACCEPTED, "Proposal state is not Accepted!"); }); }); describe('TestKingAutomatonProposals claiming reward', async() => { const KingAutomaton = artifacts.require("KingAutomaton"); - const Proposals = artifacts.require("Proposals"); beforeEach(async() => { accounts = await web3.eth.getAccounts(); @@ -445,7 +444,8 @@ describe('TestKingAutomatonProposals claiming reward', async() => { // Deploy contract and create proposal. koh = await KingAutomaton.new(slots, 16, "0x010000", "406080000", 10, -10, treasury_percentage, - PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, TIME_UNIT_IN_SECONDS); + PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, + NUM_HISTORY_PERIODS, TIME_UNIT_IN_SECONDS); await koh.setOwnerAllSlots(); id = await koh.createProposal.call(account, "", "", "0x", budget_period_len, num_periods, budget_per_period); await koh.createProposal(account, "", "", "0x", budget_period_len, num_periods, budget_per_period); @@ -648,5 +648,149 @@ describe('TestKingAutomatonProposals claiming reward', async() => { assert.equal((proposal_balance1.sub(proposal_balance2)).toNumber(), budget_per_period * num_periods, "Incorrect proposal balance!"); assert.equal(acc_balance1.toString(), acc_balance2.toString(), "Incorrect account balance!"); }); +}); + +describe('TestKingAutomatonProposals voting history 5 periods (1 word)', async() => { + const KingAutomaton = artifacts.require("KingAutomaton"); + + beforeEach(async() => { + accounts = await web3.eth.getAccounts(); + account = accounts[0]; + slots = 4; + num_periods = 5; + + // Deploy contract and create proposal. + koh = await KingAutomaton.new(slots, 16, "0x010000", "406080000", 10, -10, 2, + PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, + num_periods, TIME_UNIT_IN_SECONDS); + proposals_initial_period = 1 * await koh.proposalsInitialPeriod(); + + await koh.setOwnerAllSlots(); + id = await koh.createProposal.call(account, "", "", "0x", 30, 3, 20); + await koh.createProposal(account, "", "", "0x", 30, 3, 20); + await koh.payForGas(id, slots - 1); + await koh.castVote(id, 0, 1); // 25% / 125 + increaseTime(proposals_initial_period); + await koh.updateProposalState(id); + }); + + it("period history", async() => { + let proposal = await koh.getProposalData(id); + let history = new BN(proposal.votingHistory[0]); + assert.equal("7d", history.toString(16), "Incorrect voting history! (0)"); + + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 2, 1); // 50% / 150 + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("960000007d", history.toString(16), "Incorrect voting history! (1)"); + + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 2, 2); // 0% / 100 + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("966400007d", history.toString(16), "Incorrect voting history! (2)"); + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 3, 1); // 25% / 125 + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("96647d007d", history.toString(16), "Incorrect voting history! (3)"); + + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 0, 2); // -25% / 75 + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("96647d4b7d", history.toString(16), "Incorrect voting history! (4)"); + + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 1, 1); // 0% / 100 + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("96647d4b64", history.toString(16), "Incorrect voting history! (5)"); + + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 1, 2); // -50% / 50 + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("32647d4b64", history.toString(16), "Incorrect voting history! (6)"); + }); + + it("period history 2", async() => { + await koh.castVote(id, 1, 1); + let proposal = await koh.getProposalData(id); + let history = new BN(proposal.votingHistory[0]); + assert.equal("960000007d", history.toString(16), "Incorrect voting history! (0)"); + + await koh.castVote(id, 2, 1); // Change vote difference, but time passed < period + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("960000007d", history.toString(16), "History should not have changed! 0"); + + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, 2, 1); // Update history - the new value should be added + proposal = await koh.getProposalData(id); + let calc_difference = new BN((await koh.calcVoteDifference(id)) * 1 + 100); + history = new BN(proposal.votingHistory[0]); + assert.equal("96af00007d", history.toString(16), "Incorrect voting history! (1)"); + + // Increase time but keep the vote difference the same + increaseTime(TIME_UNIT_IN_SECONDS * 3); + proposal = await koh.getProposalData(id); + history = new BN(proposal.votingHistory[0]); + assert.equal("96af00007d", history.toString(16), "History should not have changed! 1"); + }); +}); + +describe('TestKingAutomatonProposals voting history 35 periods (2 words)', async() => { + const KingAutomaton = artifacts.require("KingAutomaton"); + + beforeEach(async() => { + accounts = await web3.eth.getAccounts(); + account = accounts[0]; + slots = 4; + num_periods = 35; + + // Deploy contract and create proposal. + koh = await KingAutomaton.new(slots, 16, "0x010000", "406080000", 10, -100, 2, + PROPOSALS_INITIAL_PERIOD, PROPOSALS_CONTEST_PERIOD, PROPOSALS_MIN_PERIOD_LEN, + num_periods, TIME_UNIT_IN_SECONDS); + proposals_initial_period = 1 * await koh.proposalsInitialPeriod(); + + await koh.setOwnerAllSlots(); + id = await koh.createProposal.call(account, "", "", "0x", 30, 3, 20); + await koh.createProposal(account, "", "", "0x", 30, 3, 20); + await koh.payForGas(id, slots - 1); + await koh.castVote(id, 0, 1); // 25% / 125 + increaseTime(proposals_initial_period); + await koh.updateProposalState(id); + }); + + it("period history", async() => { + let proposal = await koh.getProposalData(id); + let calc_difference = new BN((await koh.calcVoteDifference(id)) * 1 + 100); + let history = [calc_difference]; + let idx = 0; + let value = 0; + let offset = 0; + let mask = 0; + let word = 0; + + for (i = 1; i <= 36; i++) { + increaseTime(TIME_UNIT_IN_SECONDS); + await koh.castVote(id, i % slots, (i + Math.floor(i / slots)) % 2 + 1); + proposal = await koh.getProposalData(id); + idx = new BN(proposal.historyStartIdx); + calc_difference = new BN((await koh.calcVoteDifference(id)) * 1 + 100); + history.push(calc_difference.toString(16)); + let word_idx = Math.floor(idx.toNumber() / 32); + word = new BN(proposal.votingHistory[word_idx]); + offset = (idx % 32) * 8; + mask = new BN(255); + mask.iushln(offset); + value = word.and(mask); + value.iushrn(offset); + assert.equal(value.toString(16), calc_difference.toString(16), "Incorrect value @ step " + i.toString()); + } + }); }); diff --git a/test/TestKingAutomatonDebug.sol b/test/TestKingAutomatonDebug.sol index 0e6b59c..5dc796f 100644 --- a/test/TestKingAutomatonDebug.sol +++ b/test/TestKingAutomatonDebug.sol @@ -6,7 +6,7 @@ import "../contracts/KingAutomaton.sol"; contract KingAutomatonDebug is KingAutomaton { // Set mask to 0x10000 to trigger debug mode. - constructor() KingAutomaton (16, 4, 0x10000, 406080000, 10, -10, 2, 7, 7, 3, 24 * 60 * 60) public {} + constructor() KingAutomaton (16, 4, 0x10000, 406080000, 10, -10, 2, 7, 7, 3, 128, 24 * 60 * 60) public {} } contract TestKingAutomatonDebug { diff --git a/test/TestKingAutomatonNoDebug.sol b/test/TestKingAutomatonNoDebug.sol index 169ba89..6696488 100644 --- a/test/TestKingAutomatonNoDebug.sol +++ b/test/TestKingAutomatonNoDebug.sol @@ -6,7 +6,7 @@ import "../contracts/KingAutomaton.sol"; contract KingAutomatonNoDebug is KingAutomaton { // Set mask to 0 as it should be in the live contract. - constructor() KingAutomaton (16, 4, 0, 406080000, 10, -10, 2, 7, 7, 3, 24 * 60 * 60) public {} + constructor() KingAutomaton (16, 4, 0, 406080000, 10, -10, 2, 7, 7, 3, 128, 24 * 60 * 60) public {} } contract TestKingAutomatonDebug {