// Contract: TheLargestMultisigEver
// Target function: function submitSignatures(uint256 callIndex, bytes[] calldata signatures) external
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @dev Defines call parameters for transactions 
struct Call {
    uint256 callIndex;
    address to;
    uint256 value;
    bytes data;
}

/**
 * @title TheLargestMultisigEver
 * @author 0xth0mas
 * @notice This multisig wallet is a public multisig that allows any Ethereum
 *         address to be a signer on a transaction
 */
contract TheLargestMultisigEver {

    /// @dev Thrown when non-address(this) attempts to call external functions that must be called from address(this)
    error InvalidCaller();

    /// @dev Thrown when the call results in a revert
    error CallFailed(); 

    /// @dev Thrown when signatures are submitted for a call index greater than the current call index or when a call has already been executed
    error InvalidCall();

    /// @dev Thrown when the minimum signature threshold is being set to zero
    error NotEnoughSigners();

    /// @dev Thrown when a signature length is not 65 bytes
    error InvalidSignature();

    /// @dev Thrown when attempting to set minimum signatures greater than number of possible Ethereum addresses
    error CannotExceedPossibleAddresses();

    bytes32 constant EIP712_DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );

    bytes32 constant CALL_TYPEHASH = keccak256(
        "Call(uint256 callIndex,address to,uint256 value,bytes data)"
    );

    bytes32 public immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    string private constant _name = "TheLargestMultisigEver";
    string private constant _version = "1.0";

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    /// @dev mapping of proposed calls to their index
    mapping(uint256 callIndex => Call) public proposedCalls;

    /// @dev mapping of calls that have been executed
    mapping(uint256 callIndex => bool executed) public callExecuted;

    /// @dev bitmap to track signers that have already approved a transaction
    mapping(uint256 callIndex => mapping(uint256 bucket => uint256 signed)) public callSigners;

    /// @dev mapping of number of signers for a proposed call
    mapping(uint256 callIndex => uint256 signerCount) public callSignerCount;

    /// @dev the minimum number of valid signatures to execute a transaction
    uint168 public minimumSignatures;

    /// @dev the next index for a proposed call to be executed by the multisig
    uint88 public nextCallIndex;

    constructor() payable {
        _hashedName = keccak256(bytes(_name));
        _hashedVersion = keccak256(bytes(_version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);

        callExecuted[0] = true;
        nextCallIndex = 1;
        
        minimumSignatures = 1;
    }

    /**
     * @notice Propose a transaction to be made by the multisig.
     * @param call struct containing the details of the call transaction to execute
     * @return callIndex the index of the call that will need to be signed by at least
     *                   the minimum number of signers to be executed
     */
    function proposeCall(Call calldata call) external returns(uint256 callIndex) {
        callIndex = nextCallIndex;
        proposedCalls[callIndex] = call;
        proposedCalls[callIndex].callIndex = callIndex;
        unchecked {
            ++nextCallIndex;
        }
    }