Skip to content

feat(governance): show how many approvers still need to approve - #788

Open
pahor167 wants to merge 5 commits into
masterfrom
feat/show-remaining-approvals
Open

feat(governance): show how many approvers still need to approve#788
pahor167 wants to merge 5 commits into
masterfrom
feat/show-remaining-approvals

Conversation

@pahor167

@pahor167 pahor167 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

Nothing in celocli told an approver how many more signatures a pending approval still needs.

Worse, governance:show --proposalID reported completion as confirmations / owners. Owners is not the threshold that approves a proposal — MultiSig.required() is. On mainnet the approver multisig has 12 owners and a threshold of 3, so a fully approved proposal reads as completion: 3 / 12, which looks 25% done.

This surfaces the real numbers everywhere an approval is pending: confirmations collected, the required threshold, and how many approvers still have to sign.

  • GovernanceWrapper.getApprovalStatus returns required and remaining. completion keeps its existing meaning so SDK consumers are unaffected; ApprovalStatus is now exported.
  • governance:show --proposalID shows the new fields in the approvals map plus a note line.
  • governance:show --hotfix gained an approvals section for both approval paths (approver and security council), each with address, confirmations, required and remaining, plus a note per path.
  • multisig:show reports confirmations, confirmationsRequired and confirmationsRemaining per displayed transaction. It uses the internal threshold for transactions the multisig sends to itself, matching MultiSig.isConfirmed, and skips the fields for non-existent transaction ids.

No new flags, so no docs regeneration.

Other changes

Confirmation-progress math lives in getConfirmationProgress (utils/multisig-utils.ts) and getMultiSigApprovalProgress / getHotfixApprovalProgress (utils/governance.ts) rather than inline in the commands.

A security council or approver that is not a Celo MultiSig (mainnet's security council is a Gnosis Safe) has no required(), so it falls back to reporting a single outstanding signature. Reading a Safe's own getThreshold() is left for a follow-up.

Tested

  • packages/sdk/contractkit/src/wrappers/Governance.test.ts — new #getApprovalStatus case: remaining === required before approval, 0 with the confirming address after. Suite green (12/12).
  • packages/cli/src/utils/multisig-utils.test.ts — new unit tests for getConfirmationProgress: regular vs internal threshold, executed transactions, and no negative remainder when a threshold was lowered.
  • packages/cli/src/commands/multisig/show.test.ts — new integration test on a dedicated 3-owner multisig (required 3, internalRequired 2) proving the internal-threshold branch: proposing leaves confirmationsRemaining: 1, a second confirmation executes it and drops it to 0. Existing snapshot updated.
  • packages/cli/src/commands/governance/show.test.ts — proposal snapshot updated; new hotfix test walking pending -> approved.
  • Full @celo/celocli suite: 102/103 suites, 392 tests. The one failure is src/utils/helpers.test.ts failing to bind an anvil port under parallel workers (Address already in use); it passes in isolation and is untouched by this change.

Verified against a mainnet fork by impersonating three approver multisig signatories and approving live proposal 303:

  completion: 1 / 12          completion: 2 / 12          completion: 3 / 12
  remaining: 2                remaining: 1                remaining: 0
  required: 3                 required: 3                 required: 3
approved: false             approved: false             approved: true

with the note line tracking it: Note: 2 more approver confirmation(s) needed to approve this proposal (1/3) -> (2/3) -> gone once approved.

How to QA

Read-only against mainnet:

celocli governance:show --hotfix 0xbf670baa773b342120e1af45433a465bbd6fa289a5cf72763d63d95e4e22482d -n mainnet
# approver: isMultiSig true, required 3, remaining 3
# Note: 3 more approver confirmation(s) needed to approve this hotfix (0/3)

celocli multisig:show 0x41822d8A191fcfB1cfcA5F7048818aCd8eE933d3 --tx 258 -n mainnet
# two signers listed, confirmationsRequired 3, confirmationsRemaining 1

For the proposal view, a proposal in Referendum or Execution with at least one confirmation is needed; at the time of writing mainnet has none, so use a fork:

anvil --fork-url <archive-rpc> --port 8557 --auto-impersonate
cast rpc anvil_setBalance 0x5C73151A9eDcaccBD20EBB346bd4e419CdC94da3 0x56BC75E2D63100000 --rpc-url http://127.0.0.1:8557
celocli governance:approve --proposalID 303 --from 0x5C73151A9eDcaccBD20EBB346bd4e419CdC94da3 --useMultiSig -n http://127.0.0.1:8557
celocli governance:show --proposalID 303 -n http://127.0.0.1:8557 --raw

Note that forno rate-limits the multisig transaction scan and prunes historical state, so forking needs an archive endpoint.

Related issues

  • N/A

PR-Codex overview

This PR focuses on optimizing the MultiSigWrapper's read operations to reduce RPC load and enhance governance approval tracking within the Celo ecosystem.

Detailed summary

  • Improved getConfirmations to use a contract view for efficiency.
  • Enhanced getTransactionDataByContent to scan transactions in batches, returning the most recent match.
  • Updated CLI commands to display remaining approvals for proposals and hotfixes.
  • Modified GovernanceWrapper to return new fields for required and remaining approvals.
  • Added tests to verify new functionality in approval tracking and transaction confirmation.
  • Refactored MultiSigWrapper to optimize transaction fetching and confirmations handling.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

`governance:show` and `multisig:show` reported who can approve but not how
many signatures are still missing. The proposal view's `completion` field
counted approver multisig owners, which is not the threshold that approves a
proposal, so a fully approved proposal could read as "3 / 12".

- GovernanceWrapper.getApprovalStatus returns `required` (the approver
  multisig threshold) and `remaining`; `completion` keeps its meaning
- governance:show --proposalID prints the new fields plus a note with the
  outstanding confirmation count
- governance:show --hotfix gained an approvals section covering both the
  approver and the security council, falling back to a single signature when
  the address is not a multisig
- multisig:show reports confirmations, confirmationsRequired and
  confirmationsRemaining per transaction, using the internal threshold for
  transactions the multisig sends to itself, as MultiSig.isConfirmed does
Copilot AI review requested due to automatic review settings August 4, 2026 11:49
@pahor167
pahor167 requested a review from a team as a code owner August 4, 2026 11:49
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6d1105e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@celo/contractkit Minor
@celo/celocli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves how governance- and multisig-related approval progress is surfaced across the SDK and celocli, by reporting the true multisig threshold (required()), how many confirmations have been collected, and how many remain—avoiding the misleading “confirmations / owners” interpretation when the threshold is smaller than the owner set.

Changes:

  • SDK: Exported ApprovalStatus and extended GovernanceWrapper.getApprovalStatus to return required and remaining while preserving completion for backwards compatibility.
  • CLI: Enhanced governance:show (proposal + hotfix paths) and multisig:show to display confirmations-required/remaining and user-facing note lines for pending approvals.
  • Refactor + tests: Centralized confirmation-progress math in CLI utilities and added/updated unit + integration coverage and snapshots.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/sdk/contractkit/src/wrappers/Governance.ts Export ApprovalStatus; include required + remaining in getApprovalStatus derived from multisig threshold.
packages/sdk/contractkit/src/wrappers/Governance.test.ts Add coverage for new required/remaining behavior in getApprovalStatus.
packages/cli/src/utils/multisig-utils.ts Add getConfirmationProgress helper (regular vs internal threshold, executed handling).
packages/cli/src/utils/multisig-utils.test.ts Unit tests for getConfirmationProgress edge cases and threshold selection.
packages/cli/src/utils/governance.ts Add helpers to compute and return hotfix/multisig approval progress (approver + security council).
packages/cli/src/commands/multisig/show.ts Display per-tx confirmation progress and handle missing tx ids by omitting progress fields.
packages/cli/src/commands/multisig/show.test.ts Integration test proving internal-threshold behavior in multisig:show.
packages/cli/src/commands/governance/show.ts Print approvals section and “remaining confirmations” notes for proposals and hotfixes.
packages/cli/src/commands/governance/show.test.ts Snapshot update + new hotfix test covering pending → approved output.
.changeset/remaining-approvals.md Changeset documenting the CLI/SDK feature additions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/utils/governance.ts
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
import * from '@celo/core' (csj) 1.81 KB (0%)
import * from '@celo/core' (esm) 1.56 KB (0%)

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 67.27%. Comparing base (b4f631c) to head (6d1105e).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #788      +/-   ##
==========================================
+ Coverage   64.52%   67.27%   +2.75%     
==========================================
  Files          33      170     +137     
  Lines         919    10042    +9123     
  Branches      114     1848    +1734     
==========================================
+ Hits          593     6756    +6163     
- Misses        313     3198    +2885     
- Partials       13       88      +75     
Components Coverage Δ
celocli 50.16% <ø> (∅)
sdk 68.49% <95.00%> (∅)
wallets 73.68% <ø> (∅)
viem-sdks 94.15% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread packages/cli/src/utils/governance.ts Outdated
Comment thread packages/cli/src/utils/multisig-utils.test.ts Outdated
Comment thread packages/cli/src/utils/governance.ts
Comment thread packages/sdk/contractkit/src/wrappers/Governance.ts
Comment thread packages/cli/src/utils/governance.ts
Pavel Hornak added 2 commits August 5, 2026 10:20
getConfirmations issued one confirmations() read per owner; the contract
already exposes getConfirmations(txId) returning the full list in one call.

getTransactionDataByContent fetched the multisig's entire transaction history
in a single Promise.all (268 parallel requests on the mainnet approver
multisig), which is enough to get rate-limited by public RPCs. It now scans
newest-first in small batches and stops at the first match; the wanted
transaction is almost always recent. When several transactions share the same
content, the most recent one is now returned instead of the oldest.
- Classify the hotfix approver with getCode() instead of probing required()
  and catching the revert. An EOA, a Celo MultiSig, a Gnosis Safe and an
  unknown contract are now distinct kinds; for a Safe the real getThreshold()
  is reported instead of pretending a single signature suffices.
- Fetch the multisig threshold once per approval path and pass it down,
  instead of reading required() twice.
- For an approved hotfix, resolve the confirmation list from the executed
  multisig transaction instead of returning an empty placeholder.
- Unify ConfirmationProgress on required/remaining to match ApprovalStatus;
  multisig:show keeps its prefixed display keys, which would otherwise be
  ambiguous next to the contract-wide required-confirmations lines.
- Use synthetic addresses in the multisig-utils unit tests; the previous
  constants were real mainnet accounts.
Copilot AI review requested due to automatic review settings August 5, 2026 08:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/cli/src/utils/multisig-utils.ts:27

  • getConfirmationProgress correctly accounts for the multisig’s internalRequired threshold (when the tx destination is the multisig itself), but viewConfirmationStatus (used by multisig:approve / multisig:transfer) still reads only required(). As a result, for self-targeted transactions (e.g. add/remove owner) the CLI can misreport how many confirmations are needed and whether the next approval will execute the tx, even though multisig:show now reports the internal-threshold numbers.
export async function getConfirmationProgress(
  readMultisig: Pick<ConfirmationGetters, 'getConfirmations'>,
  txIndex: bigint,
  transaction: { destination: Address; executed: boolean },
  thresholds: { required: bigint; internalRequired: bigint },
  multisigAddress: Address

Cover getTransactionDataByContent against a stubbed contract: first-batch hit,
multi-batch continuation, newest-of-duplicates, exhaustive miss, and value
matching, plus the single-read getConfirmations. Also lifts the diff coverage
of the batched-scan change above the patch threshold, which the anvil-based
Governance tests alone misattribute under the es6 async transform.
Copilot AI review requested due to automatic review settings August 5, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Exercise submitOrConfirmTransaction (confirm-existing and submit-new paths),
getTransaction with confirmations, getTransactions, the owner and threshold
getters and the write helpers against the stubbed contract, bringing the
wrapper to near-full line coverage.
Copilot AI review requested due to automatic review settings August 5, 2026 10:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/cli/src/commands/multisig/show.ts:58

  • multisig:show currently treats destination === zeroAddress as “transaction does not exist”. That can incorrectly hide confirmation progress for a real transaction that intentionally targets the zero address. A safer existence check is to compare txId against txCount (which you already fetched).
      if (destination === zeroAddress) {
        // transaction does not exist, there is nothing to confirm
        return {}
      }

.changeset/remaining-approvals.md:11

  • The release note for governance:show --hotfix says it reports progress towards thresholds, but it doesn’t mention the new Safe-specific behavior (reading getThreshold() when required() is unavailable). Adding that detail here will match what the code actually does and set expectations that partial Safe progress isn’t visible.
- `governance:show --hotfix` gained an `approvals` section with the approver and security
  council progress towards their respective thresholds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants