Skip to content

Conversation

@czareko
Copy link
Collaborator

@czareko czareko commented Jan 16, 2026

@czareko czareko marked this pull request as ready for review January 22, 2026 01:10
@czareko czareko marked this pull request as draft January 22, 2026 04:26
@czareko czareko marked this pull request as ready for review January 26, 2026 04:46
@czareko czareko requested review from illuzen and n13 January 26, 2026 04:46
@n13
Copy link
Collaborator

n13 commented Jan 27, 2026

One thin - apparently we auto clean expired proposals on propose.

This risks timing out when too many proposals are expired locking up the entire pallet.

So I would put a reasonable limit, like at most clean up 5 expired proposals on each propose call, then we never have this issue.

Edit: I see it's capped by 200.

pub const MaxActiveProposals: u32 = 100; // Max active proposals per multisig
pub const MaxTotalProposalsInStorage: u32 = 200; // Max total in storage (Active + Executed + Cancelled)

Aren't proposals automatically deleted on Cancel and execute?

If so, then cancelled and executed states are never stored, also we only need max active, the other value is then not needed.

Which would then cap the removal process at 99, which I guess is fine, although if you manage to fill it up to 100, no new proposals are possible, and therefore nothing gets cleaned up either.

@n13
Copy link
Collaborator

n13 commented Jan 27, 2026

Review of PR #352: Custom Multisig Pallet

Target Branch: testnet/planck
Spec: Multisig Pallet - Requirements Specification

Summary

This PR implements the Custom Multisig Pallet as per the specification. The implementation generally aligns well with the requirements, covering the core lifecycle, economic security models (burned fees, locked deposits), and storage limits.

However, there is a Critical Issue regarding threshold-1 multisigs and a few observations for configuration and testing.

Critical Findings

1. Missing Auto-Execution for Threshold-1 Proposals

The specification states in the propose API requirements:

"If threshold = 1, auto-executes and immediately removes proposal"

The docstring for propose in src/lib.rs also claims this behavior:

"If threshold = 1, auto-executes and immediately removes proposal"

Issue: The implementation in propose (lines 501-673) does NOT implement this logic. It creates the proposal, adds the proposer's approval, stores it, and returns. It never checks if threshold == 1 to trigger do_execute.

Impact:

  • 1-of-N Multisigs: Requires a second transaction (approve) from another signer to execute, defeating the purpose of 1-of-N.
  • 1-of-1 Multisigs: The proposer is automatically added to approvals during propose. The approve function checks !proposal.approvals.contains(&approver). Since the proposer is already in the list, they cannot call approve. Since there are no other signers, the proposal is permanently stuck in storage until it expires.

Recommendation:
Add the check at the end of propose:

// ... inside propose ...
Proposals::<T>::insert(&multisig_address, proposal_id, proposal.clone());
// ... update counters ...

// Check for auto-execution
if multisig_data.threshold == 1 {
    Self::do_execute(multisig_address, proposal_id, proposal)?;
}

Self::deposit_event(Event::ProposalCreated { ... });
Ok(())

Observations & Suggestions

1. Fee Configuration

In runtime/src/configs/mod.rs:

  • MultisigFee (Creation): 100 * MILLI_UNIT (0.1 UNIT)
  • ProposalFee (Action): 1000 * MILLI_UNIT (1 UNIT)

Observation: The Proposal Fee is 10x higher than the Multisig Creation Fee. Typically, creating a permanent entity (Multisig) is more expensive than using it. While the spec mentions ProposalFee is the "Primary revenue" and "Spam prevention", ensure this balance is intended. A cheap creation fee might encourage spamming empty multisig accounts (though MultisigDeposit helps mitigate this).

2. Test Coverage

  • Threshold-1 Test: There is no test case for a Multisig with threshold = 1. Adding a test case for create_multisig(threshold=1) -> propose() would have caught the stuck proposal issue.
  • Filibuster Protection: The tests for per_signer_proposal_limit_enforced are good and verify the spec requirement.

3. Spec Compliance Checklist

Requirement Status Notes
Multisig Account
Deterministic Address Uses sorted signers + nonce + TrailingZeroInput
Immutable Config Signers/Threshold set on creation
Unique Identification Global nonce ensures uniqueness
Proposal Lifecycle
Full Call Storage Stores BoundedVec<u8>
Auto-Execution Missing for Threshold=1
Auto-Cleanup (Active) propose cleans expired proposals
Economics
Burned Fees Uses withdraw (burned in typical config)
Locked Deposits Uses reserve/unreserve
Security
Reentrancy Protection do_execute uses CEI pattern correctly
Spam Prevention Limits on signers, proposals, call size enforced
Filibuster Protection Per-signer limits enforced

Conclusion

The PR is high quality but must not be merged until the Threshold-1 execution bug is fixed, as it renders 1-of-1 multisigs unusable.

@czareko
Copy link
Collaborator Author

czareko commented Jan 27, 2026

@n13 , Threshold=1 case fixed, MaxActiveProposals - removed

Copy link
Collaborator

@n13 n13 left a comment

Choose a reason for hiding this comment

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

GTG

@czareko czareko merged commit 1c2b978 into testnet/planck Jan 27, 2026
4 checks passed
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