Skip to content

Latest commit

 

History

History
112 lines (74 loc) · 5.59 KB

File metadata and controls

112 lines (74 loc) · 5.59 KB

Multi-Signature Wallet

What is Multi Signature Wallet?

A Multi-Signature (MultiSig) wallet is a smart contract that requires multiple parties to agree on a transaction before it can be executed. Unlike a standard wallet controlled by a single private key (Single-of-One), a MultiSig adds a layer of security by requiring a $M$-of-$N$ consensus (e.g., 2 out of 3 signers must approve).

This is widely used by DAOs, project teams, and security-conscious individuals to prevent a single point of failure. If one signer's key is compromised, the funds remain safe.

Core Mechanisms

1. Proposals and Submission

Anyone can submit a transaction proposal to the contract. The transaction is stored in a PENDING state and assigned a unique IdCount.

    function Submit(address _address, address _token, uint256 input, Tx_Type _type) public {
        require(_type != Tx_Type.NONE, InvalidTransactionType());
        if(_type == Tx_Type.ETH) {}

Upon submission, if the sender is one of the authorized signers, the contract automatically records their approval via _tryAutoConfirm.

2. Confirmation (Voting)

Authorized signers must call the confirm function for a specific Transaction ID.

  • Signer Validation: Only addresses in the Signers array can confirm.
  • Double-Voting Prevention: The hasConfirmed mapping ensures a signer cannot vote twice for the same transaction.
    function confirm(uint256 _txId) public
    checkSigner(msg.sender)
    checkId(_txId) {}

3. Execution

Once the number of approvals reaches the defined Threshold, the execute function can be called. This function performs the actual logic based on the Tx_Type:

  • ETH: Transfers Ether from the contract to a recipient.
  • ERC20: Transfers standard tokens using the transfer interface.
  • ADD_SIGNER / REMOVE_SIGNER: Modifies the list of authorized controllers.
  • THRESHOLD: Updates the minimum number of approvals required for future transactions.
function execute(uint256 _txId) public
    checkId(_txId) {}

Transaction Types

The contract handles more than just moving money; it manages its own governance:

Type Action Parameters Used
ETH Send Native Currency _address (Receiver), input (Amount)
ERC20 Send Tokens _token (Contract), _address (Receiver), input (Amount)
ADD_SIGNER Add new admin _address (New Signer)
REMOVE_SIGNER Remove admin _address (Target Signer)
THRESHOLD Change logic input (New Threshold value)

Security Features & Constraints

Constructor Safeguards

At deployment, the contract enforces strict rules to prevent "bricking" (locking) the wallet:

  • No Zero Addresses: Prevents burning ownership to the null address.
  • Redundancy Check: Ensures the same address isn't added twice to inflate the signer count.
  • Minimum Signers: Requires at least 2 signers to ensure it is actually a "multi" sig.
  • Threshold Validation: The threshold must be at least 2 and cannot exceed the total number of signers.

Custom Errors

Instead of gas-heavy strings, this contract uses Custom Errors (e.g., error ThresholdNotMet()). This makes transactions significantly cheaper for the signers because the EVM doesn't have to store and revert long string messages.

Technical Details

State Tracking

The contract uses two primary enums to manage logic flow:

  • Tx_Type: Categorizes what the transaction intends to do (ETH, ERC20, etc.).
  • States: Tracks if a transaction is NONE, PENDING, or EXECUTED. Once EXECUTED, a transaction cannot be run again, protecting against Reentrancy or Double-Spend attacks.

Internal Logic

  • ETH Transfers: Uses .call{value: _amount}("") which is the modern standard for sending ETH, allowing for flexible gas stipends.
  • Array Management: When a signer is removed, the contract moves the last element of the array into the removed spot and pops the end (Signers[i] = Signers[len-1];). This is a gas-efficient way to delete from an unordered array without shifting every element.

Testing Coverage

Token-erc20

A contract TokenERC20 is deployed and used to test ERC20 Transactions.

The provided test suite ensures the contract behaves as expected under various conditions:

  • Deployment: Validates that invalid thresholds or redundant signers cause a revert.
  • Submission Logic: Checks that ETH and ERC20 proposals require the correct inputs (e.g., you can't propose an ERC20 transfer without a token address).
  • Execution Flow: * Verifies funds actually move after execution.
    • Ensures the Threshold is strictly enforced.
    • Confirms that state updates to EXECUTED properly.
  • Edge Cases: Tests for insufficient ETH balance and unauthorized confirmation attempts.

Usage Summary

  1. Deploy: Provide a list of addresses and a threshold (e.g., [A, B, C], 2).
  2. Fund: Send ETH or ERC20 tokens to the contract address.
  3. Propose: Call Submit with the desired action.
  4. Approve: Other signers call confirm(txId).
  5. Finalize: Once approvals $\ge$ Threshold, anyone calls execute(txId).