diff --git a/test/functional/feature_on_chain_government_voting_scenarios.py b/test/functional/feature_on_chain_government_voting_scenarios.py new file mode 100755 index 0000000000..aa09fb6757 --- /dev/null +++ b/test/functional/feature_on_chain_government_voting_scenarios.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test OCG voting scenarios""" + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import ( + assert_equal, +) + +APPROVAL_THRESHOLD=50 +QUORUM=50 +VOTING_PERIOD=10 + +class OCGVotingScenarionTest(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [ + ['-jellyfish_regtest=1', '-dummypos=0', '-txnotokens=0', '-amkheight=50', '-bayfrontheight=51', '-eunosheight=80', '-fortcanningheight=82', '-fortcanninghillheight=84', '-fortcanningroadheight=86', '-fortcanningcrunchheight=88', '-fortcanningspringheight=90', '-fortcanninggreatworldheight=94', '-grandcentralheight=101', '-simulatemainnet=1'], + ] + + def setup_masternodes(self, nMasternodes = 19): + self.nodes[0].mns = [] + operatorAddresses = [] + + for _ in range(nMasternodes): + address = self.nodes[0].getnewaddress('', 'legacy') + self.nodes[0].mns.append(self.nodes[0].createmasternode(address)) + operatorAddresses.append(address) + self.nodes[0].generate(1) + + self.nodes[0].generate(20) # Enables all MNs + self.sync_blocks(timeout=120) + + # restart node with masternode_operator addresses to be able to mint with every MNs + self.restart_node(0, self.nodes[0].extra_args + ['-masternode_operator={}'.format(address) for address in operatorAddresses]) + + # Mint with every MNs to meet voting eligibility criteria + for address in operatorAddresses: + self.nodes[0].generatetoaddress(1, address) + + def setup(self): + # Generate chain + self.nodes[0].generate(100) + self.sync_blocks(timeout=120) + + self.setup_masternodes() + + # activate on-chain governance + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/feature/gov':'true', + 'v0/gov/proposals/voting_period': '{}'.format(VOTING_PERIOD), + }}) + self.nodes[0].generate(1) + + def test_vote_on_cfp(self, yesVote, noVote, neutralVote, expectedStatus): + height = self.nodes[0].getblockcount() + + # Create address for CFP + address = self.nodes[0].getnewaddress() + context = "" + title = "Create test community fund proposal" + amount = 100 + + # Create CFP + propId = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": amount, "cycles": 1, "payoutAddress": address}) + self.nodes[0].generate(1) + + mnIterator = iter(self.nodes[0].mns) + + for _ in range(yesVote): + mnId = next(mnIterator) + self.nodes[0].votegov(propId, mnId, 'yes') + + for _ in range(noVote): + mnId = next(mnIterator) + self.nodes[0].votegov(propId, mnId, 'no') + + for _ in range(neutralVote): + mnId = next(mnIterator) + self.nodes[0].votegov(propId, mnId, 'neutral') + + self.nodes[0].generate(1) + + self.nodes[0].generate(VOTING_PERIOD * 2) + proposal = self.nodes[0].getgovproposal(propId) + + assert_equal(proposal['status'], expectedStatus) + + self.rollback_to(height) + + def test_scenario_below_approval_threshold(self, expectedStatus): + self.test_vote_on_cfp(yesVote=4, noVote=6, neutralVote=2, expectedStatus=expectedStatus) + + def test_scenario_at_approval_threshold(self, expectedStatus): + self.test_vote_on_cfp(yesVote=8, noVote=8, neutralVote=0, expectedStatus=expectedStatus) + + def test_scenario_above_approval_threshold(self, expectedStatus): + self.test_vote_on_cfp(yesVote=10, noVote=6, neutralVote=2, expectedStatus=expectedStatus) + + def test_scenario_below_quorum(self, expectedStatus): + self.test_vote_on_cfp(yesVote=6, noVote=2, neutralVote=1, expectedStatus=expectedStatus) + + def test_scenario_at_quorum(self, expectedStatus): + self.test_vote_on_cfp(yesVote=6, noVote=2, neutralVote=2, expectedStatus=expectedStatus) + + def test_scenario_above_quorum(self, expectedStatus): + self.test_vote_on_cfp(yesVote=6, noVote=3, neutralVote=2, expectedStatus=expectedStatus) + + def test_scenario_high_neutral_vote(self, expectedStatus): + self.test_vote_on_cfp(yesVote=8, noVote=3, neutralVote=5, expectedStatus=expectedStatus) + + def test_scenario_only_yes_and_neutral(self, expectedStatus): + self.test_vote_on_cfp(yesVote=8, noVote=0, neutralVote=8, expectedStatus=expectedStatus) + + def test_scenario_66_6_percent_approval_full_yes_votes(self, expectedStatus): + self.test_vote_on_cfp(yesVote=len(self.nodes[0].mns), noVote=0, neutralVote=0, expectedStatus=expectedStatus) + + def test_scenario_66_6_percent_approval_full_no_votes(self, expectedStatus): + self.test_vote_on_cfp(yesVote=0, noVote=len(self.nodes[0].mns), neutralVote=0, expectedStatus=expectedStatus) + + def test_scenario_66_6_percent_approval_full_neutral_votes(self, expectedStatus): + self.test_vote_on_cfp(yesVote=0, noVote=0, neutralVote=len(self.nodes[0].mns), expectedStatus=expectedStatus) + + def scenarios_test(self): + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/gov/proposals/cfp_approval_threshold':'{}%'.format(APPROVAL_THRESHOLD), + }}) + self.nodes[0].generate(1) + + self.test_scenario_below_approval_threshold(expectedStatus='Rejected') + self.test_scenario_at_approval_threshold(expectedStatus='Rejected') + self.test_scenario_above_approval_threshold(expectedStatus='Completed') + + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/gov/proposals/quorum':'{}%'.format(QUORUM), + }}) + self.nodes[0].generate(1) + + self.test_scenario_below_quorum(expectedStatus='Rejected') + self.test_scenario_at_quorum(expectedStatus='Rejected') + self.test_scenario_above_quorum(expectedStatus='Completed') + + # Currently marked as Rejected as neutral votes are incorrectly counted as no + # Should assert that it's Completed once https://github.com/DeFiCh/ain/issues/1704 is fixed + self.test_scenario_high_neutral_vote(expectedStatus='Rejected') + + # Currently marked as Rejected as neutral votes are incorrectly counted as no + # Should assert that it's Completed once https://github.com/DeFiCh/ain/issues/1704 is fixed + self.test_scenario_only_yes_and_neutral(expectedStatus='Rejected') + + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/gov/proposals/cfp_approval_threshold':'{}%'.format(66.6), + }}) + self.nodes[0].generate(1) + + self.test_scenario_66_6_percent_approval_full_yes_votes(expectedStatus="Completed") + self.test_scenario_66_6_percent_approval_full_no_votes(expectedStatus="Rejected") + self.test_scenario_66_6_percent_approval_full_neutral_votes(expectedStatus="Rejected") + + def run_test(self): + + self.setup() + + self.scenarios_test() + +if __name__ == '__main__': + OCGVotingScenarionTest().main () diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 2c75c87fa1..af1fdbfaaf 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -303,6 +303,7 @@ 'feature_on_chain_government.py', 'feature_on_chain_government_voting_period_alignment.py', 'feature_on_chain_government_fee_distribution.py', + 'feature_on_chain_government_voting_scenarios.py', 'rpc_listgovproposals.py', 'rpc_help.py', 'feature_help.py',