4

after a council motion has reached its configured threshold (required number of votes collected), the polkadot.js interface enables a × close button which, it appears, anyone may interact with.

  • is closing a motion something that anyone can trigger or does the polkadot.js interface just make it look like that is the case? prior to the threshold being reached, the button is disabled.
  • does closing a motion cause it to conclude earlier than waiting for the voting end date to occur? if not, it seems redundant to provide the mechanism. if so, it seems odd that the mechanism may be triggered by a casual observer.

can anyone shed light on the purpose of the close button and its underlying logic?

council motion close interaction on polkadot.js

1 Answer 1

5

It closes a motion that has enough Aye or Nay votes. The reason it doesn't auto-close is that it would be extra logic on every single vote: looking up to see if it has been passed or rejected. Anyone can call it.

Its behavior is in the docs: https://paritytech.github.io/substrate/master/pallet_collective/pallet/enum.Call.html#variant.close

Most pertinent to your question:

  1. May be called by any signed account in order to finish voting and close the proposal.
  2. If called before the end of the voting period it will only close the vote if it is has enough votes to be approved or disapproved.

EDIT: Answering your follow up question here to allow for more characters.

The voting on the proposal closes after the voting period, but somebody still has to call the close function. The close function is broken into two primary paths: one for if it's called before the end of the voting period, and one for if it's called after. Each path then has sub-paths for what to do if the proposal is approved or not.

We can generally break down the close function into some chunks:

First, some checks/variable setting. Note specifically that both approved and disapproved can be false.

        pub fn close(...) -> DispatchResultWithPostInfo {
            let _ = ensure_signed(origin)?;

            let voting = Self::voting(&proposal_hash).ok_or(Error::<T, I>::ProposalMissing)?;
            ensure!(voting.index == index, Error::<T, I>::WrongIndex);

            let mut no_votes = voting.nays.len() as MemberCount;
            let mut yes_votes = voting.ayes.len() as MemberCount;
            let seats = Self::members().len() as MemberCount;
            let approved = yes_votes >= voting.threshold;
            let disapproved = seats.saturating_sub(no_votes) < voting.threshold;

The next segment checks if one of them is already true (before the voting period is closed). Note that so far, the function hasn't even checked if the voting period is open or closed, it's only checked if there are enough votes to approve or disapprove the proposal.

            // Allow (dis-)approving the proposal as soon as there are enough votes.
            if approved {
                /* snip */
                let (proposal_weight, proposal_count) =
                    Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal);
                return Ok(/* snip */)
            } else if disapproved {
                Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
                let proposal_count = Self::do_disapprove_proposal(proposal_hash);
                return Ok(/* snip */)
            }

Assuming it has not been approved or disapproved, then it checks if the voting period is over:

            // Only allow actual closing of the proposal after the voting period has ended.
            ensure!(
                frame_system::Pallet::<T>::block_number() >= voting.end,
                Error::<T, I>::TooEarly
            );

And finally, if the voting period has ended then it applies the prime voter logic and determines whether or not it's been approved:

            let prime_vote = Self::prime().map(|who| voting.ayes.iter().any(|a| a == &who));

            // default voting strategy.
            let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats);

            let abstentions = seats - (yes_votes + no_votes);
            match default {
                true => yes_votes += abstentions,
                false => no_votes += abstentions,
            }
            let approved = yes_votes >= voting.threshold;

            if approved {
                let (proposal, len) = Self::validate_and_get_proposal(
                    &proposal_hash,
                    length_bound,
                    proposal_weight_bound,
                )?;
                Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
                let (proposal_weight, proposal_count) =
                    Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal);
                Ok(/* snip */)
            } else {
                Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
                let proposal_count = Self::do_disapprove_proposal(proposal_hash);
                Ok(/* snip */)
            }
        }
2
  • thanks for taking the time to answer. i see your point about the extra logic in performing the lookup. i should have also asked: what happens to motions which are not closed by drive-by observers? do they close at voting end or are they lost to the ether? on reading your answer i went ahead and closed the motions in the screenshot, paying the transaction fees for doing so. if i hadn't and nobody else did, what would happen?
    – grenade
    Mar 25, 2022 at 14:16
  • 1
    Answered in the main body^^ Mar 26, 2022 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.