// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.8.4;
/// @notice Simple gas-optimized multi-signature contract.
contract Multisig {
    event Propose(address indexed proposer, uint256 indexed proposal);
    event Sign(address indexed signer, uint256 indexed proposal);
    event Execute(uint256 indexed proposal);

    error NotSigner();
    error Signed();
    error InsufficientSigs();
    error ExecuteFailed();

    uint256 sigsRequired;
    uint256 proposalCounter;

    mapping(address => bool) public signer;
    mapping(uint256 => Proposal) public proposals;
    mapping(uint256 => mapping(address => bool)) public signed;

    struct Proposal {
        address target;
        uint256 value;
        bytes payload;
        uint256 sigs;
    }

    constructor(address[] memory signers_, uint256 sigsRequired_) {
        // cannot realistically overflow on human timescales
        unchecked {
            for (uint256 i; i < signers_.length; i++) {
                signer[signers_[i]] = true;
            }
        }     
        sigsRequired = sigsRequired_;
    }

    function propose(address target, uint256 value, bytes calldata payload) external {
        // cannot realistically overflow on human timescales
        unchecked {
            uint256 proposal = proposalCounter++;

            proposals[proposal] = Proposal({
                target: target,
                value: value,
                payload: payload,
                sigs: 0
            });

            emit Propose(msg.sender, proposal);
        }
    }

    function sign(uint256 proposal) external {
        if (!signer[msg.sender]) revert NotSigner();
        if (signed[proposal][msg.sender]) revert Signed();
        
        // cannot realistically overflow on human timescales
        unchecked {
            proposals[proposal].sigs++;
        }
        
        signed[proposal][msg.sender] = true;

        emit Sign(msg.sender, proposal);
    }

    function execute(uint256 proposal) external returns (bool success, bytes memory result) {
        Proposal storage prop = proposals[proposal];

        if (prop.sigs < sigsRequired) revert InsufficientSigs();

        (success, result) = prop.target.call{value: prop.value}(prop.payload);

        if (!success) revert ExecuteFailed();
        
        delete proposals[proposal];

        emit Execute(proposal);
    }

    receive() external payable virtual {}
}