<gh_stars>1-10
import sys

from celo_sdk.contracts.base_wrapper import BaseWrapper
from celo_sdk.registry import Registry

from web3 import Web3


class Election(BaseWrapper):
    """
    Contract for voting for validators and managing validator groups

    Attributes:
        web3: Web3
            Web3 object
        registry: Registry
            Registry object
        address: str
            Contract's address
        abi: list
            Contract's ABI
        wallet: Wallet
            Wallet object to sign transactions
    """

    def __init__(self, web3: Web3, registry: Registry, address: str, abi: list, wallet: 'Wallet' = None):
        super().__init__(web3, registry, wallet=wallet)
        self.web3 = web3
        self.address = address
        self._contract = self.web3.eth.contract(self.address, abi=abi)
        self.__wallet = wallet

    def electable_validators(self) -> dict:
        """
        Returns the minimum and maximum number of validators that can be elected
        """
        min_max = self._contract.functions.electableValidators().call()
        return { 'min': min_max[0], 'max': min_max[1]}
    
    def electability_threshold(self) -> int:
        """
        Returns the current election threshold
        """
        return self._contract.functions.getElectabilityThreshold().call()

    def validator_signer_address_from_set(self, index: int, block_number: int) -> str:
        """
        Gets a validator address from the validator set at the given block number

        Parameters:
            index: int
                Index of requested validator in the validator set
            block_number: int
                Block number to retrieve the validator set from
        Returns:
            Address of validator at the requested index
        """
        return self._contract.functions.validatorSignerAddressFromSet(index, block_number).call()
    
    def validator_signer_address_from_current_set(self, index: int) -> str:
        """
        Gets a validator address from the current validator set
        
        Parameters:
            index: int
                Index of requested validator in the validator set
        Returns:
            Address of validator at the requested index
        """
        return self._contract.functions.validatorSignerAddressFromCurrentSet(index).call()
    
    def number_validators_in_set(self, block_number: int) -> int:
        """
        Gets the size of the validator set that must sign the given block number

        Parameters:
            block_number: int
                Block number to retrieve the validator set from
        Returns:
            Size of the validator set
        """
        return self._contract.functions.numberValidatorsInSet(block_number).call()
    
    def number_validators_in_current_set(self) -> int:
        """
        Gets the size of the current elected validator set

        Returns:
            Size of the current elected validator set
        """
        return self._contract.functions.numberValidatorsInCurrentSet().call()
    
    def get_total_votes(self) -> int:
        """
        Returns the total votes received across all groups

        Returns:
            The total votes received across all groups
        """
        return self._contract.functions.getTotalVotes().call()
    
    def get_current_validator_signers(self) -> list:
        """
        Returns the current validator signers using the precompiles

        Returns:
            List of current validator signers
        """
        return self._contract.functions.getCurrentValidatorSigners().call()
    
    def get_validator_signers(self, block_number: int) -> list:
        """
        Returns the validator signers for block `blockNumber`

        Parameters:
            block_number: int
                Block number to retrieve signers for
        Returns:
            Address of each signer in the validator set
        """
        num_validators = self.number_validators_in_set(block_number)

        results = []
        for ind in range(num_validators):
            results.append(self.validator_signer_address_from_set(ind, block_number))
        
        return results
    
    def elect_validator_signers(self, min: int = None, max: int = None) -> list:
        """
        Returns a list of elected validators with seats allocated to groups via the D'Hondt method

        Parameters:
            min: int
            max: int
        Returns:
            The list of elected validators
        """
        if not min or not max:
            config = self.get_config()
            min_arg = min if min else config['min']
            max_arg = max if max else config['max']
            return self._contract.functions.electNValidatorSigners(min_arg, max_arg).call()
        else:
            return self._contract.functions.electValidatorSigners().call()
    
    def get_total_votes_for_group(self, group: str, block_number: int = None) -> int:
        """
        Returns the total votes for `group`

        Parameters:
            goup: str
                The address of the validator group
            block_number: int
        Returns:
            The total votes for `group`
        """
        if block_number:
            votes = self._contract.functions.getTotalVotesForGroup(group).call(block_identifier=block_number)
        else:
            votes = self._contract.functions.getTotalVotesForGroup(group).call()
        
        return votes
    
    def get_total_votes_for_group_by_account(self, group: str, account: str) -> int:
        """
        Returns the total votes for `group` made by `account`

        Parameters:
            group: str
                The address of the validator group
            account: str
                The address of the voting account
        Returns:
            The total votes for `group` made by `account`
        """
        return self._contract.functions.getTotalVotesForGroupByAccount(group, account).call()

    def get_active_votes_for_group(self, group: str, block_number: int = None) -> int:
        """
        Returns the active votes for `group`

        Parameters:
            group: str
                The address of the validator group
            block_number: int
        Returns:
            The active votes for `group`
        """
        if block_number:
            votes = self._contract.functions.getActiveVotesForGroup(group).call(block_identifier=block_number)
        else:
            votes = self._contract.functions.getActiveVotesForGroup(group).call()
        
        return votes
    
    def get_groups_voted_for_by_account(self, account: str) -> list:
        """
        Returns the groups that `account` has voted for

        Parameters:
            account: str
                The address of the account casting votes
        Returns:
            The groups that `account` has voted for
        """
        return self._contract.functions.getGroupsVotedForByAccount().call()
    
    def get_votes_for_group_by_account(self, account: str, group: str, block_number: int = None) -> dict:
        """
        Returns votes for group by specific account

        Parameters:
            account: str
            group: str
            block_number: int
        """
        if block_number:
            pending = self._contract.functions.getPendingVotesForGroupByAccount(group, account).call(block_identifier=block_number)
            active = self._contract.functions.getActiveVotesForGroupByAccount(group, account).call(block_identifier=block_number)
        else:
            pending = self._contract.functions.getPendingVotesForGroupByAccount(group, account).call()
            active = self._contract.functions.getActiveVotesForGroupByAccount(group, account).call()
        
        return { 'group': group, 'pending': pending, 'active': active }
    
    def get_voter(self, account: str, block_number: int = None) -> dict:
        """
        Parameters:
            account: str
            block_number: int
        """
        if block_number:
            groups = self._contract.functions.getGroupsVotedForByAccount(account).call(block_identifier=block_number)
        else:
            groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        
        votes = []
        for el in groups:
            votes.append(self.get_votes_for_group_by_account(account, el, block_number=block_number))
        
        return { 'address': account, 'votes': votes }
    
    def hash_pending_votes(self, account: str) -> bool:
        """
        Returns whether or not the account has any pending votes

        Parameters:
            account: str
                The address of the account casting votes
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        is_pending = []
        for g in groups:
            is_pending.append(self._contract.functions.getPendingVotesForGroupByAccount(g,account).call() > 0)
        
        return True in is_pending
    
    def has_activatable_pending_votes(self, account: str) -> bool:
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        is_activatable = []
        for g in groups:
            is_activatable.append(self._contract.functions.hasActivatablePendingVotes(account, g).call())
        
        return True in is_activatable
    
    def get_config(self) -> dict:
        """
        Returns current configuration parameters
        """
        electability_threshold = self.electability_threshold()
        total_votes = self.get_total_votes()

        return {
            'electable_validators': self.electable_validators(),
            'electability_threshold': electability_threshold,
            'max_num_groups_voted_for': self._contract.functions.maxNumGroupsVotedFor().call(),
            'total_votes': total_votes,
            'current_threshold': total_votes * electability_threshold
        }
    
    def get_validator_group_votes(self, address: str) -> dict:
        votes = self._contract.functions.getTotalVotesForGroup(address).call()
        eligible = self._contract.functions.getGroupEligibility(address).call()
        num_votes_receivable = self._contract.functions.getNumVotesReceivable(address).call()
        accounts_contract = self.create_and_get_contract_by_name('Accounts')
        name = accounts_contract.get_name(address)

        return {
            'address': address,
            'name': name,
            'votes': votes,
            'capacity': num_votes_receivable - votes,
            'eligible': eligible
        }
    
    def get_validator_groups_votes(self) -> list:
        """
        Returns the current registered validator groups and their total votes and eligibility
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')
        groups = validators_contract.get_registered_validator_groups_addresses()
        result = []
        for g in groups:
            result.append(self.get_validator_group_votes(g))
        
        return result
    
    def _activate(self, group: str) -> str:
        func_call = self._contract.functions.activate(group)
        return self.__wallet.send_transaction(func_call)

    def activate(self, account: str) -> list:
        """
        Activates any activatable pending votes

        Parameters:
            account: str
                The account with pending votes to activate
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()

        is_activatable = []
        for g in groups:
            is_activatable.append(self._contract.functions.hasActivatablePendingVotes(account, g).call())

        groups_activatable = []
        for (addrs, is_actv) in zip(groups, is_activatable):
            if is_actv:
                groups_activatable.append(addrs)
        
        activates = [self._activate(g) for g in groups_activatable]

        return activates

    def revoke_pending(self, account: str, group: str, value: int) -> str:
        """
        Parameters:
            account: str
            group: str
            value: int
        Returns:
            Transaction hash
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        index = groups.index(group)
        lesser_greater = self.find_lesser_and_greater_after_vote(group, value * -1)
        
        func_call = self._contract.functions.revokePending(group, value, lesser_greater['lesser'], lesser_greater['greater'], index)
        return self.__wallet.send_transaction(func_call)

    def revoke_active(self, account: str, group: str, value: int) -> str:
        """
        Parameters:
            account: str
            group: str
            value: int
        Returns:
            Transaction hash
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        index = groups.index(group)
        lesser_greater = self.find_lesser_and_greater_after_vote(group, value * -1)
        
        func_call = self._contract.functions.revokeActive(group, value, lesser_greater['lesser'], lesser_greater['greater'], index)
        return self.__wallet.send_transaction(func_call)
    
    def revoke(self, account: str, group: str, value: int) -> list:
        """
        Parameters:
            account: str
            group: str
            value: int
        Returns:
            list
        """
        vote = self.get_votes_for_group_by_account(account, group)
        if value > vote['pending'] + vote['active']:
            raise Exception(f"Can't revoke more votes for {group} than have been made by {account}")
        
        txos = []
        pending_value = min(vote['pending'], value)
        if pending_value > 0:
            txos.append(self.revoke_pending(account, group, pending_value))
        if pending_value < value:
            active_value = value - pending_value
            txos.append(self.revoke_active(account, group, active_value))
        
        return txos
    
    def vote(self, validator_group: str, value: int) -> str:
        """
        Increments the number of total and pending votes for `group`

        Parameters:
            validator_group: str
                The validator group to vote for
            value: int
                The amount of gold to use to vote
        Returns:
            Transaction hash
        """
        lesser_greater = self.find_lesser_and_greater_after_vote(validator_group, value)
        func_call = self._contract.functions.vote(validator_group, value, lesser_greater['lesser'], lesser_greater['greater'])

        return self.__wallet.send_transaction(func_call)
    
    def get_eligible_validator_groups_votes(self) -> list:
        """
        Returns the current eligible validator groups and their total votes
        """
        res = self._contract.functions.getTotalVotesForEligibleValidatorGroups().call()
        length = min(len(res[0], res[1]))
        result = []
        
        for ind in range(length):
            result.append({ 
                'address': res[0][ind],
                'name': '',
                'votes': res[1][ind],
                'capacity': 0,
                'eligible': True
             })
        
        return result
    
    def find_lesser_and_greater_after_vote(self, voted_group: str, vote_weight: int) -> dict:
        current_votes = self.get_eligible_validator_groups_votes()
        selected_group = None
        for vote in current_votes:
            if vote['address'] == voted_group:
                selected_group = vote
                break
        vote_total = vote_weight if not selected_group else selected_group['votes'] + vote_weight

        greater_key = self.null_address
        lesser_key = self.null_address

        for vote in current_votes:
            if vote['address'] != voted_group:
                if vote['votes'] <= vote_total:
                    lesser_key = vote['address']
                    break
                greater_key = vote['address']
        
        return { 'lesser': lesser_key, 'greater': greater_key }
    
    def get_elected_validators(self, epoch_number: int) -> list:
        """
        Retrieves the set of validatorsparticipating in BFT at epochNumber

        Parameters:
            epoch_number: int
                The epoch to retrieve the elected validator set at
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')

        block_number = validators_contract.get_first_block_number_for_epoch(epoch_number)
        signers = self.get_validator_signers(block_number)
        result = []

        for s in signers:
            result.append(validators_contract.get_validator_from_signer(s))
        
        return result
    
    def get_group_voter_rewards(self, epoch_number: int) -> list:
        """
        Retrieves GroupVoterRewards at epochNumber

        Parameters:
            epoch_number: int
                The epoch to retrieve GroupVoterRewards at
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')

        block_number = validators_contract.get_last_block_number_for_epoch(epoch_number)
        events = self._contract.events.EpochRewardsDistributedToVoters.getLogs(fromBlock=block_number, toBlock=block_number)
        if not events:
            raise Exception("There are no events found for this epoch number")
        
        validators_contract = self.create_and_get_contract_by_name('Validators')
        
        validator_group = []
        for event in events:
            validator_group.append(validators_contract.get_validator_group(event['args']['group'], False))
        
        result = []
        for ind, event in enumerate(events):
            result.append({ 'epoch_number': epoch_number, 'group': validator_group[ind], 'group_voter_payment': event['args']['value'] })
        
        return result

    def get_voter_rewards(self, address: str, epoch_number: int, voter_share: dict = None) -> dict:
        """
        Retrieves VoterRewards for address at epochNumber

        Parameters:
            address: str
                The address to retrieve VoterRewards for
            epoch_number: int
                The epoch to retrieve VoterRewards at
            voter_share: dict
                Optionally address' share of group rewards
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')

        active_vote_share = voter_share if voter_share else self.get_voter_share(address, validators_contract.get_last_block_number_for_epoch(epoch_number))

        group_voter_rewards = self.get_group_voter_rewards(epoch_number)

        voter_rewards = []
        for g in group_voter_rewards:
            if g['group'].lstrip('0x').lower() in active_vote_share:
                voter_rewards.append(g)
        
        result = []
        for v_r in voter_rewards:
            group = v_r['group']['address'].lstrip('0x').lower()
            result.append({
                'address': address,
                'address_payment': v_r['group_voter_payment'] * active_vote_share[group],
                'group': v_r['group'],
                'epoch_number': v_r['epoch_number']
            })
        
        return result

    def get_voter_share(self, address: str, block_number: int = None) -> dict:
        """
        Retrieves a voter's share of active votes

        Parameters:
            address: str
                The voter to retrieve share for
            block_number: int
                The block to retrieve the voter's share at
        """
        active_voter_votes = {}
        voter = self.get_voter(address, block_number)
        for vote in voter['votes']:
            group = vote['group'].lstrip('0x').lower()
            active_voter_votes[group] = vote['active']

        for addrs, number in active_voter_votes.items():
            active_voter_votes[addrs] = number / self.get_active_votes_for_group(address, block_number)
        
        return active_voter_votes

import sys

from celo_sdk.contracts.base_wrapper import BaseWrapper
from celo_sdk.registry import Registry

from web3 import Web3


class Election(BaseWrapper):
    """
    Contract for voting for validators and managing validator groups

    Attributes:
        web3: Web3
            Web3 object
        registry: Registry
            Registry object
        address: str
            Contract's address
        abi: list
            Contract's ABI
        wallet: Wallet
            Wallet object to sign transactions
    """

    def __init__(self, web3: Web3, registry: Registry, address: str, abi: list, wallet: 'Wallet' = None):
        super().__init__(web3, registry, wallet=wallet)
        self.web3 = web3
        self.address = address
        self._contract = self.web3.eth.contract(self.address, abi=abi)
        self.__wallet = wallet

    def electable_validators(self) -> dict:
        """
        Returns the minimum and maximum number of validators that can be elected
        """
        min_max = self._contract.functions.electableValidators().call()
        return { 'min': min_max[0], 'max': min_max[1]}
    
    def electability_threshold(self) -> int:
        """
        Returns the current election threshold
        """
        return self._contract.functions.getElectabilityThreshold().call()

    def validator_signer_address_from_set(self, index: int, block_number: int) -> str:
        """
        Gets a validator address from the validator set at the given block number

        Parameters:
            index: int
                Index of requested validator in the validator set
            block_number: int
                Block number to retrieve the validator set from
        Returns:
            Address of validator at the requested index
        """
        return self._contract.functions.validatorSignerAddressFromSet(index, block_number).call()
    
    def validator_signer_address_from_current_set(self, index: int) -> str:
        """
        Gets a validator address from the current validator set
        
        Parameters:
            index: int
                Index of requested validator in the validator set
        Returns:
            Address of validator at the requested index
        """
        return self._contract.functions.validatorSignerAddressFromCurrentSet(index).call()
    
    def number_validators_in_set(self, block_number: int) -> int:
        """
        Gets the size of the validator set that must sign the given block number

        Parameters:
            block_number: int
                Block number to retrieve the validator set from
        Returns:
            Size of the validator set
        """
        return self._contract.functions.numberValidatorsInSet(block_number).call()
    
    def number_validators_in_current_set(self) -> int:
        """
        Gets the size of the current elected validator set

        Returns:
            Size of the current elected validator set
        """
        return self._contract.functions.numberValidatorsInCurrentSet().call()
    
    def get_total_votes(self) -> int:
        """
        Returns the total votes received across all groups

        Returns:
            The total votes received across all groups
        """
        return self._contract.functions.getTotalVotes().call()
    
    def get_current_validator_signers(self) -> list:
        """
        Returns the current validator signers using the precompiles

        Returns:
            List of current validator signers
        """
        return self._contract.functions.getCurrentValidatorSigners().call()
    
    def get_validator_signers(self, block_number: int) -> list:
        """
        Returns the validator signers for block `blockNumber`

        Parameters:
            block_number: int
                Block number to retrieve signers for
        Returns:
            Address of each signer in the validator set
        """
        num_validators = self.number_validators_in_set(block_number)

        results = []
        for ind in range(num_validators):
            results.append(self.validator_signer_address_from_set(ind, block_number))
        
        return results
    
    def elect_validator_signers(self, min: int = None, max: int = None) -> list:
        """
        Returns a list of elected validators with seats allocated to groups via the D'Hondt method

        Parameters:
            min: int
            max: int
        Returns:
            The list of elected validators
        """
        if not min or not max:
            config = self.get_config()
            min_arg = min if min else config['min']
            max_arg = max if max else config['max']
            return self._contract.functions.electNValidatorSigners(min_arg, max_arg).call()
        else:
            return self._contract.functions.electValidatorSigners().call()
    
    def get_total_votes_for_group(self, group: str, block_number: int = None) -> int:
        """
        Returns the total votes for `group`

        Parameters:
            goup: str
                The address of the validator group
            block_number: int
        Returns:
            The total votes for `group`
        """
        if block_number:
            votes = self._contract.functions.getTotalVotesForGroup(group).call(block_identifier=block_number)
        else:
            votes = self._contract.functions.getTotalVotesForGroup(group).call()
        
        return votes
    
    def get_total_votes_for_group_by_account(self, group: str, account: str) -> int:
        """
        Returns the total votes for `group` made by `account`

        Parameters:
            group: str
                The address of the validator group
            account: str
                The address of the voting account
        Returns:
            The total votes for `group` made by `account`
        """
        return self._contract.functions.getTotalVotesForGroupByAccount(group, account).call()

    def get_active_votes_for_group(self, group: str, block_number: int = None) -> int:
        """
        Returns the active votes for `group`

        Parameters:
            group: str
                The address of the validator group
            block_number: int
        Returns:
            The active votes for `group`
        """
        if block_number:
            votes = self._contract.functions.getActiveVotesForGroup(group).call(block_identifier=block_number)
        else:
            votes = self._contract.functions.getActiveVotesForGroup(group).call()
        
        return votes
    
    def get_groups_voted_for_by_account(self, account: str) -> list:
        """
        Returns the groups that `account` has voted for

        Parameters:
            account: str
                The address of the account casting votes
        Returns:
            The groups that `account` has voted for
        """
        return self._contract.functions.getGroupsVotedForByAccount().call()
    
    def get_votes_for_group_by_account(self, account: str, group: str, block_number: int = None) -> dict:
        """
        Returns votes for group by specific account

        Parameters:
            account: str
            group: str
            block_number: int
        """
        if block_number:
            pending = self._contract.functions.getPendingVotesForGroupByAccount(group, account).call(block_identifier=block_number)
            active = self._contract.functions.getActiveVotesForGroupByAccount(group, account).call(block_identifier=block_number)
        else:
            pending = self._contract.functions.getPendingVotesForGroupByAccount(group, account).call()
            active = self._contract.functions.getActiveVotesForGroupByAccount(group, account).call()
        
        return { 'group': group, 'pending': pending, 'active': active }
    
    def get_voter(self, account: str, block_number: int = None) -> dict:
        """
        Parameters:
            account: str
            block_number: int
        """
        if block_number:
            groups = self._contract.functions.getGroupsVotedForByAccount(account).call(block_identifier=block_number)
        else:
            groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        
        votes = []
        for el in groups:
            votes.append(self.get_votes_for_group_by_account(account, el, block_number=block_number))
        
        return { 'address': account, 'votes': votes }
    
    def hash_pending_votes(self, account: str) -> bool:
        """
        Returns whether or not the account has any pending votes

        Parameters:
            account: str
                The address of the account casting votes
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        is_pending = []
        for g in groups:
            is_pending.append(self._contract.functions.getPendingVotesForGroupByAccount(g,account).call() > 0)
        
        return True in is_pending
    
    def has_activatable_pending_votes(self, account: str) -> bool:
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        is_activatable = []
        for g in groups:
            is_activatable.append(self._contract.functions.hasActivatablePendingVotes(account, g).call())
        
        return True in is_activatable
    
    def get_config(self) -> dict:
        """
        Returns current configuration parameters
        """
        electability_threshold = self.electability_threshold()
        total_votes = self.get_total_votes()

        return {
            'electable_validators': self.electable_validators(),
            'electability_threshold': electability_threshold,
            'max_num_groups_voted_for': self._contract.functions.maxNumGroupsVotedFor().call(),
            'total_votes': total_votes,
            'current_threshold': total_votes * electability_threshold
        }
    
    def get_validator_group_votes(self, address: str) -> dict:
        votes = self._contract.functions.getTotalVotesForGroup(address).call()
        eligible = self._contract.functions.getGroupEligibility(address).call()
        num_votes_receivable = self._contract.functions.getNumVotesReceivable(address).call()
        accounts_contract = self.create_and_get_contract_by_name('Accounts')
        name = accounts_contract.get_name(address)

        return {
            'address': address,
            'name': name,
            'votes': votes,
            'capacity': num_votes_receivable - votes,
            'eligible': eligible
        }
    
    def get_validator_groups_votes(self) -> list:
        """
        Returns the current registered validator groups and their total votes and eligibility
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')
        groups = validators_contract.get_registered_validator_groups_addresses()
        result = []
        for g in groups:
            result.append(self.get_validator_group_votes(g))
        
        return result
    
    def _activate(self, group: str) -> str:
        func_call = self._contract.functions.activate(group)
        return self.__wallet.send_transaction(func_call)

    def activate(self, account: str) -> list:
        """
        Activates any activatable pending votes

        Parameters:
            account: str
                The account with pending votes to activate
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()

        is_activatable = []
        for g in groups:
            is_activatable.append(self._contract.functions.hasActivatablePendingVotes(account, g).call())

        groups_activatable = []
        for (addrs, is_actv) in zip(groups, is_activatable):
            if is_actv:
                groups_activatable.append(addrs)
        
        activates = [self._activate(g) for g in groups_activatable]

        return activates

    def revoke_pending(self, account: str, group: str, value: int) -> str:
        """
        Parameters:
            account: str
            group: str
            value: int
        Returns:
            Transaction hash
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        index = groups.index(group)
        lesser_greater = self.find_lesser_and_greater_after_vote(group, value * -1)
        
        func_call = self._contract.functions.revokePending(group, value, lesser_greater['lesser'], lesser_greater['greater'], index)
        return self.__wallet.send_transaction(func_call)

    def revoke_active(self, account: str, group: str, value: int) -> str:
        """
        Parameters:
            account: str
            group: str
            value: int
        Returns:
            Transaction hash
        """
        groups = self._contract.functions.getGroupsVotedForByAccount(account).call()
        index = groups.index(group)
        lesser_greater = self.find_lesser_and_greater_after_vote(group, value * -1)
        
        func_call = self._contract.functions.revokeActive(group, value, lesser_greater['lesser'], lesser_greater['greater'], index)
        return self.__wallet.send_transaction(func_call)
    
    def revoke(self, account: str, group: str, value: int) -> list:
        """
        Parameters:
            account: str
            group: str
            value: int
        Returns:
            list
        """
        vote = self.get_votes_for_group_by_account(account, group)
        if value > vote['pending'] + vote['active']:
            raise Exception(f"Can't revoke more votes for {group} than have been made by {account}")
        
        txos = []
        pending_value = min(vote['pending'], value)
        if pending_value > 0:
            txos.append(self.revoke_pending(account, group, pending_value))
        if pending_value < value:
            active_value = value - pending_value
            txos.append(self.revoke_active(account, group, active_value))
        
        return txos
    
    def vote(self, validator_group: str, value: int) -> str:
        """
        Increments the number of total and pending votes for `group`

        Parameters:
            validator_group: str
                The validator group to vote for
            value: int
                The amount of gold to use to vote
        Returns:
            Transaction hash
        """
        lesser_greater = self.find_lesser_and_greater_after_vote(validator_group, value)
        func_call = self._contract.functions.vote(validator_group, value, lesser_greater['lesser'], lesser_greater['greater'])

        return self.__wallet.send_transaction(func_call)
    
    def get_eligible_validator_groups_votes(self) -> list:
        """
        Returns the current eligible validator groups and their total votes
        """
        res = self._contract.functions.getTotalVotesForEligibleValidatorGroups().call()
        length = min(len(res[0], res[1]))
        result = []
        
        for ind in range(length):
            result.append({ 
                'address': res[0][ind],
                'name': '',
                'votes': res[1][ind],
                'capacity': 0,
                'eligible': True
             })
        
        return result
    
    def find_lesser_and_greater_after_vote(self, voted_group: str, vote_weight: int) -> dict:
        current_votes = self.get_eligible_validator_groups_votes()
        selected_group = None
        for vote in current_votes:
            if vote['address'] == voted_group:
                selected_group = vote
                break
        vote_total = vote_weight if not selected_group else selected_group['votes'] + vote_weight

        greater_key = self.null_address
        lesser_key = self.null_address

        for vote in current_votes:
            if vote['address'] != voted_group:
                if vote['votes'] <= vote_total:
                    lesser_key = vote['address']
                    break
                greater_key = vote['address']
        
        return { 'lesser': lesser_key, 'greater': greater_key }
    
    def get_elected_validators(self, epoch_number: int) -> list:
        """
        Retrieves the set of validatorsparticipating in BFT at epochNumber

        Parameters:
            epoch_number: int
                The epoch to retrieve the elected validator set at
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')

        block_number = validators_contract.get_first_block_number_for_epoch(epoch_number)
        signers = self.get_validator_signers(block_number)
        result = []

        for s in signers:
            result.append(validators_contract.get_validator_from_signer(s))
        
        return result
    
    def get_group_voter_rewards(self, epoch_number: int) -> list:
        """
        Retrieves GroupVoterRewards at epochNumber

        Parameters:
            epoch_number: int
                The epoch to retrieve GroupVoterRewards at
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')

        block_number = validators_contract.get_last_block_number_for_epoch(epoch_number)
        events = self._contract.events.EpochRewardsDistributedToVoters.getLogs(fromBlock=block_number, toBlock=block_number)
        if not events:
            raise Exception("There are no events found for this epoch number")
        
        validators_contract = self.create_and_get_contract_by_name('Validators')
        
        validator_group = []
        for event in events:
            validator_group.append(validators_contract.get_validator_group(event['args']['group'], False))
        
        result = []
        for ind, event in enumerate(events):
            result.append({ 'epoch_number': epoch_number, 'group': validator_group[ind], 'group_voter_payment': event['args']['value'] })
        
        return result

    def get_voter_rewards(self, address: str, epoch_number: int, voter_share: dict = None) -> dict:
        """
        Retrieves VoterRewards for address at epochNumber

        Parameters:
            address: str
                The address to retrieve VoterRewards for
            epoch_number: int
                The epoch to retrieve VoterRewards at
            voter_share: dict
                Optionally address' share of group rewards
        """
        validators_contract = self.create_and_get_contract_by_name('Validators')

        active_vote_share = voter_share if voter_share else self.get_voter_share(address, validators_contract.get_last_block_number_for_epoch(epoch_number))

        group_voter_rewards = self.get_group_voter_rewards(epoch_number)

        voter_rewards = []
        for g in group_voter_rewards:
            if g['group'].lstrip('0x').lower() in active_vote_share:
                voter_rewards.append(g)
        
        result = []
        for v_r in voter_rewards:
            group = v_r['group']['address'].lstrip('0x').lower()
            result.append({
                'address': address,
                'address_payment': v_r['group_voter_payment'] * active_vote_share[group],
                'group': v_r['group'],
                'epoch_number': v_r['epoch_number']
            })
        
        return result

    def get_voter_share(self, address: str, block_number: int = None) -> dict:
        """
        Retrieves a voter's share of active votes

        Parameters:
            address: str
                The voter to retrieve share for
            block_number: int
                The block to retrieve the voter's share at
        """
        active_voter_votes = {}
        voter = self.get_voter(address, block_number)
        for vote in voter['votes']:
            group = vote['group'].lstrip('0x').lower()
            active_voter_votes[group] = vote['active']

        for addrs, number in active_voter_votes.items():
            active_voter_votes[addrs] = number / self.get_active_votes_for_group(address, block_number)
        
        return active_voter_votes
Contract for voting for validators and managing validator groups      Attributes:         web3: Web3             Web3 object         registry: Registry             Registry object         address: str             Contract's address         abi: list             Contract's ABI         wallet: Wallet             Wallet object to sign transactions Returns the minimum and maximum number of validators that can be elected Returns the current election threshold Gets a validator address from the validator set at the given block number          Parameters:             index: int                 Index of requested validator in the validator set             block_number: int                 Block number to retrieve the validator set from         Returns:             Address of validator at the requested index Gets a validator address from the current validator set                  Parameters:             index: int                 Index of requested validator in the validator set         Returns:             Address of validator at the requested index Gets the size of the validator set that must sign the given block number          Parameters:             block_number: int                 Block number to retrieve the validator set from         Returns:             Size of the validator set Gets the size of the current elected validator set          Returns:             Size of the current elected validator set Returns the total votes received across all groups          Returns:             The total votes received across all groups Returns the current validator signers using the precompiles          Returns:             List of current validator signers Returns the validator signers for block `blockNumber`          Parameters:             block_number: int                 Block number to retrieve signers for         Returns:             Address of each signer in the validator set Returns a list of elected validators with seats allocated to groups via the D'Hondt method          Parameters:             min: int             max: int         Returns:             The list of elected validators Returns the total votes for `group`          Parameters:             goup: str                 The address of the validator group             block_number: int         Returns:             The total votes for `group` Returns the total votes for `group` made by `account`          Parameters:             group: str                 The address of the validator group             account: str                 The address of the voting account         Returns:             The total votes for `group` made by `account` Returns the active votes for `group`          Parameters:             group: str                 The address of the validator group             block_number: int         Returns:             The active votes for `group` Returns the groups that `account` has voted for          Parameters:             account: str                 The address of the account casting votes         Returns:             The groups that `account` has voted for Returns votes for group by specific account          Parameters:             account: str             group: str             block_number: int Parameters:             account: str             block_number: int Returns whether or not the account has any pending votes          Parameters:             account: str                 The address of the account casting votes Returns current configuration parameters Returns the current registered validator groups and their total votes and eligibility Activates any activatable pending votes          Parameters:             account: str                 The account with pending votes to activate Parameters:             account: str             group: str             value: int         Returns:             Transaction hash Parameters:             account: str             group: str             value: int         Returns:             Transaction hash Parameters:             account: str             group: str             value: int         Returns:             list Increments the number of total and pending votes for `group`          Parameters:             validator_group: str                 The validator group to vote for             value: int                 The amount of gold to use to vote         Returns:             Transaction hash Returns the current eligible validator groups and their total votes Retrieves the set of validatorsparticipating in BFT at epochNumber          Parameters:             epoch_number: int                 The epoch to retrieve the elected validator set at Retrieves GroupVoterRewards at epochNumber          Parameters:             epoch_number: int                 The epoch to retrieve GroupVoterRewards at Retrieves VoterRewards for address at epochNumber          Parameters:             address: str                 The address to retrieve VoterRewards for             epoch_number: int                 The epoch to retrieve VoterRewards at             voter_share: dict                 Optionally address' share of group rewards Retrieves a voter's share of active votes          Parameters:             address: str                 The voter to retrieve share for             block_number: int                 The block to retrieve the voter's share at