ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Multisig Wallet for Secure Fund Management

Solidity

Goal -- WPM

Ready
Exercise Algorithm Area
1pragma solidity ^0.8.0;
2
3contract MultisigWallet {
4struct Transaction {
5address destination;
6uint256 value;
7bytes data;
8bool executed;
9uint256 numConfirmations;
10mapping(address => bool) confirmations;
11}
12
13address[] public owners;
14uint256 public requiredConfirmations;
15mapping(uint256 => Transaction) public transactions;
16uint256 public transactionCount;
17
18event Deposit(address indexed sender, uint256 value);
19event Submission(uint256 indexed transactionId);
20event Confirmation(address indexed sender, uint256 indexed transactionId);
21event Revocation(address indexed sender, uint256 indexed transactionId);
22event Execution(uint256 indexed transactionId);
23
24modifier onlyOwner() {
25require(isOwner(msg.sender), "Not an owner");
26_;
27}
28
29modifier transactionExists(uint256 _transactionId) {
30require(_transactionId < transactionCount, "Transaction does not exist");
31_;
32}
33
34modifier notConfirmed(uint256 _transactionId, address _owner) {
35require(!transactions[_transactionId].confirmations[_owner], "Transaction already confirmed by this owner");
36_;
37}
38
39modifier notExecuted(uint256 _transactionId) {
40require(!transactions[_transactionId].executed, "Transaction already executed");
41_;
42}
43
44constructor(address[] memory _owners, uint256 _requiredConfirmations) {
45require(_owners.length > 0, "Owners required");
46require(_requiredConfirmations > 0 && _requiredConfirmations <= _owners.length, "Invalid number of required confirmations");
47
48for (uint i = 0; i < _owners.length; i++) {
49require(_owners[i] != address(0), "Owner cannot be the zero address");
50// Ensure unique owners (optional but good practice)
51for (uint j = 0; j < i; j++) {
52require(_owners[i] != _owners[j], "Duplicate owner");
53}
54owners.push(_owners[i]);
55}
56requiredConfirmations = _requiredConfirmations;
57}
58
59function submitTransaction(address _destination, uint256 _value, bytes memory _data) public onlyOwner {
60uint256 transactionId = transactionCount;
61transactions[transactionId] = Transaction({
62destination: _destination,
63value: _value,
64data: _data,
65executed: false,
66numConfirmations: 0
67});
68transactionCount++;
69emit Submission(transactionId);
70}
71
72function confirmTransaction(uint256 _transactionId) public onlyOwner transactionExists(_transactionId) notConfirmed(_transactionId, msg.sender) notExecuted(_transactionId) {
73Transaction storage transaction = transactions[_transactionId];
74transaction.confirmations[msg.sender] = true;
75transaction.numConfirmations++;
76emit Confirmation(msg.sender, _transactionId);
77}
78
79function revokeConfirmation(uint256 _transactionId) public onlyOwner transactionExists(_transactionId) notExecuted(_transactionId) {
80require(transactions[_transactionId].confirmations[msg.sender], "Transaction not confirmed by this owner");
81Transaction storage transaction = transactions[_transactionId];
82transaction.confirmations[msg.sender] = false;
83transaction.numConfirmations--;
84emit Revocation(msg.sender, _transactionId);
85}
86
87function executeTransaction(uint256 _transactionId) public onlyOwner transactionExists(_transactionId) notExecuted(_transactionId) {
88Transaction storage transaction = transactions[_transactionId];
89require(transaction.numConfirmations >= requiredConfirmations, "Not enough confirmations");
90
91transaction.executed = true;
92(bool success, ) = transaction.destination.call{value: transaction.value}(transaction.data);
93require(success, "Transaction failed");
94
95emit Execution(_transactionId);
96}
97
98function isOwner(address _address) internal view returns (bool) {
99for (uint i = 0; i < owners.length; i++) {
100if (owners[i] == _address) {
101return true;
102}
103}
104return false;
105}
106
107// Fallback function to receive Ether
108receive() external payable {
109emit Deposit(msg.sender, msg.value);
110}
111}
Algorithm description viewbox

Multisig Wallet for Secure Fund Management

Algorithm description:

This contract implements a multisignature wallet, a secure way to manage funds where multiple parties must approve a transaction before it can be executed. It requires a predefined number of owners to confirm a proposed transaction, significantly reducing the risk of unauthorized access or single points of failure.

Algorithm explanation:

The `MultisigWallet` contract requires a specified number of `owners` to confirm a transaction before it can be executed. Transactions are proposed using `submitTransaction`, which stores the destination, value, and data, and assigns a unique ID. Owners can then `confirmTransaction` for a specific ID. The `confirmations` mapping tracks which owner has confirmed which transaction. Once the `numConfirmations` reaches `requiredConfirmations`, the transaction can be `executeTransaction`d. This function also marks the transaction as `executed` to prevent re-execution. The `onlyOwner` modifier ensures only authorized addresses can submit, confirm, revoke, or execute. The `receive` function allows the wallet to accept Ether deposits. Time complexity for submitting, confirming, revoking, and executing is O(1) on average, but can be O(N) for `isOwner` check if N is large (number of owners). Space complexity is O(T * D + O) where T is the number of transactions, D is the average size of transaction data, and O is the number of owners. Edge cases include invalid owner counts, duplicate owners, non-existent transactions, already confirmed/executed transactions, and failed external calls. The invariant is that `transaction.executed` is true only after `numConfirmations` meets `requiredConfirmations` and the external call succeeds.

Pseudocode:

Contract MultisigWallet:
  Struct Transaction:
    destination: address
    value: uint256
    data: bytes
    executed: bool
    numConfirmations: uint256
    confirmations: mapping(address => bool)

  State:
    owners: array of address
    requiredConfirmations: uint256
    transactions: mapping(uint256 => Transaction)
    transactionCount: uint256

  Constructor(_owners, _requiredConfirmations):
    Require _owners is not empty
    Require _requiredConfirmations is valid
    Add unique owners to owners array
    Set requiredConfirmations

  Function submitTransaction(destination, value, data):
    Require msg.sender is owner
    Let transactionId = transactionCount
    Create new Transaction with details
    Add Transaction to transactions mapping
    Increment transactionCount
    Emit Submission event

  Function confirmTransaction(transactionId):
    Require msg.sender is owner
    Require transactionId exists
    Require msg.sender has not confirmed this transactionId
    Require transactionId is not executed
    Set transactions[transactionId].confirmations[msg.sender] to true
    Increment transactions[transactionId].numConfirmations
    Emit Confirmation event

  Function revokeConfirmation(transactionId):
    Require msg.sender is owner
    Require transactionId exists
    Require msg.sender has confirmed this transactionId
    Set transactions[transactionId].confirmations[msg.sender] to false
    Decrement transactions[transactionId].numConfirmations
    Emit Revocation event

  Function executeTransaction(transactionId):
    Require msg.sender is owner
    Require transactionId exists
    Require transactionId is not executed
    Require transactions[transactionId].numConfirmations >= requiredConfirmations
    Set transactions[transactionId].executed to true
    Call destination with value and data
    Require call was successful
    Emit Execution event

  Internal Function isOwner(address):
    Loop through owners array, return true if address matches
    Return false otherwise

  Fallback function receive():
    Emit Deposit event