contract MbdaBoard {
    uint256 public minVotes; //minimum number of votes to execute a proposal

    mapping(address => bool) public boardMembers; //board members
    address[] public boardMembersList; // array with member addresses

    /// @dev types of proposal allowed they are Solidity function signatures (bytes4) default ones are added on deploy later more can be added through a proposal
    mapping(string => bytes4) public proposalTypes;
    uint256 public totalProposals;

    /// @notice proposal Struct
    struct Proposal {
        string proposalType;
        address payable destination;
        uint256 value;
        uint8 votes;
        bool executed;
        bool exists;
        bytes proposal; /// @dev ABI encoded parameters for the function of the proposal type
        bool success;
        bytes returnData;
        mapping(address => bool) voters;
    }

    mapping(uint256 => Proposal) public proposals;

    /// @dev restricts calls to board members
    modifier onlyBoardMember() {
        require(boardMembers[msg.sender], "Sender must be a Board Member");
        _;
    }

    /// @dev restricts calls to the board itself (these can only be called from a voted proposal)
    modifier onlyBoard() {
        require(msg.sender == address(this), "Sender must the Board");
        _;
    }

    /// @dev Events
    event NewProposal(
        uint256 proposalID,
        string indexed proposalType,
        bytes proposalPayload
    );
    event Voted(address boardMember, uint256 proposalId);
    event ProposalApprovedAndEnforced(
        uint256 proposalID,
        bytes payload,
        bool success,
        bytes returnData
    );
    event Deposit(uint256 value);

    /**
     * @dev Constructor
     * @param _initialMembers - Initial board's members
     * @param _minVotes - minimum votes to approve a proposal
     * @param _proposalTypes - Proposal types to add upon deployment
     * @param _ProposalTypeDescriptions - Description of the proposal types
     */
    constructor(
        address[] memory _initialMembers,
        uint256 _minVotes,
        bytes4[] memory _proposalTypes,
        string[] memory _ProposalTypeDescriptions
    ) public {
        require(
            _initialMembers.length >= _minVotes,
            "Member list length must be equal or higher than minVotes"
        );
        for (uint256 i = 0; i < _initialMembers.length; i++) {
            require(
                !boardMembers[_initialMembers[i]],
                "Duplicate Board Member sent"
            );
            boardMembersList.push(_initialMembers[i]);
            boardMembers[_initialMembers[i]] = true;
        }
        minVotes = _minVotes;

        // setting up default proposalTypes (board management txs)
        proposalTypes["addProposalType"] = 0xeaa0dff1;
        proposalTypes["removeProposalType"] = 0x746d26b5;
        proposalTypes["changeMinVotes"] = 0x9bad192a;
        proposalTypes["addBoardMember"] = 0x1eac03ae;
        proposalTypes["removeBoardMember"] = 0x39a169f9;
        proposalTypes["replaceBoardMember"] = 0xbec44b4f;

        // setting up user provided approved proposalTypes
        if (_proposalTypes.length > 0) {
            require(
                _proposalTypes.length == _ProposalTypeDescriptions.length,
                "Proposal types and descriptions do not match"
            );
            for (uint256 i = 0; i < _proposalTypes.length; i++)
                proposalTypes[_ProposalTypeDescriptions[i]] = _proposalTypes[i];
        }
    }

    /**
     * @dev Adds a proposal and vote on it (onlyMember)
     * @notice every proposal is a transaction to be executed by the board transaction type of proposal have to be previously approved (function sig)
     * @param _type - proposal type
     * @param _data - proposal data (ABI encoded)
     * @param _destination - address to send the transaction to
     * @param _value - value of the transaction
     * @return proposalID The ID of the proposal
     */
    function addProposal(
        string memory _type,
        bytes memory _data,
        address payable _destination,
        uint256 _value
    ) public onlyBoardMember returns (uint256 proposalID) {
        require(proposalTypes[_type] != bytes4(0x0), "Invalid proposal type");
        totalProposals++;
        proposalID = totalProposals;

        Proposal memory prop = Proposal(
            _type,
            _destination,
            _value,
            0,
            false,
            true,
            _data,
            false,
            bytes("")
        );
        proposals[proposalID] = prop;
        emit NewProposal(proposalID, _type, _data);

        // proposer automatically votes
        require(vote(proposalID), "Voting on the new proposal failed");
        return proposalID;
    }

    /**
     * @dev Vote on a given proposal (onlyMember)
     * @param _proposalID - Proposal ID
     * @return True if success
     */
    function vote(uint256 _proposalID) public onlyBoardMember returns (bool) {
        require(proposals[_proposalID].exists, "The proposal is not found");
        require(
            !proposals[_proposalID].voters[msg.sender],
            "This board member has voted already"
        );
        require(
            !proposals[_proposalID].executed,
            "This proposal has been approved and enforced"
        );

        proposals[_proposalID].votes++;
        proposals[_proposalID].voters[msg.sender] = true;
        emit Voted(msg.sender, _proposalID);

        if (proposals[_proposalID].votes >= minVotes)
            executeProposal(_proposalID);

        return true;
    }

    /**
     * @dev Executes a proposal (internal)
     * @param _proposalID - Proposal ID
     */
    function executeProposal(uint256 _proposalID) internal {
        Proposal memory prop = proposals[_proposalID];
        bytes memory payload = abi.encodePacked(
            proposalTypes[prop.proposalType],
            prop.proposal
        );
        proposals[_proposalID].executed = true;
        (bool success, bytes memory returnData) = prop.destination.call{value: prop.value}(payload);
        proposals[_proposalID].success = success;
        proposals[_proposalID].returnData = returnData;
        emit ProposalApprovedAndEnforced(
            _proposalID,
            payload,
            success,
            returnData
        );
    }

    /**
     * @dev Adds a proposal type (onlyBoard)
     * @param _id - The name of the proposal Type
     * @param _signature - 4 byte signature of the function to be called
     * @return True if success
     */
    function addProposalType(string memory _id, bytes4 _signature)
        public
        onlyBoard
        returns (bool)
    {
        proposalTypes[_id] = _signature;
        return true;
    }

    /**
     * @dev Removes a proposal type (onlyBoard)
     * @param _id - The name of the proposal Type
     * @return True if success
     */
    function removeProposalType(string memory _id)
        public
        onlyBoard
        returns (bool)
    {
        proposalTypes[_id] = bytes4("");
        return true;
    }

    /**
     * @dev Changes the amount of votes needed to approve a proposal (onlyBoard)
     * @param _minVotes - New minimum quorum to approve proposals
     * @return True if success
     */
    function changeMinVotes(uint256 _minVotes) public onlyBoard returns (bool) {
        require(_minVotes > 0, "MinVotes cannot be less than 0");
        require(
            _minVotes <= boardMembersList.length,
            "MinVotes lower than number of members"
        );
        minVotes = _minVotes;
        return true;
    }

    /**
     * @dev Adds a board member (onlyBoard)
     * @param _newMember - New member to be added
     * @return True if success
     */
    function addBoardMember(address _newMember)
        public
        onlyBoard
        returns (bool)
    {
        require(!boardMembers[_newMember], "Duplicate Board Member sent");
        boardMembersList.push(_newMember);
        boardMembers[_newMember] = true;
        if (boardMembersList.length > 1 && minVotes == 0) {
            minVotes = 1;
        }
        return true;
    }

    /**
     * @dev Removes a board member (onlyBoard)
     * @param _member - Member to be added
     * @return True if success
     */
    function removeBoardMember(address _member)
        public
        onlyBoard
        returns (bool)
    {
        boardMembers[_member] = false;
        for (uint256 i = 0; i < boardMembersList.length; i++) {
            if (boardMembersList[i] == _member) {
                boardMembersList[i] = boardMembersList[boardMembersList.length -
                    1];
                boardMembersList.pop();
            }
        }
        if (boardMembersList.length < minVotes) {
            minVotes = boardMembersList.length;
        }
        return true;
    }

    /**
     * @dev Replaces a board member (onlyBoard)
     * @param _oldMember - Old member to be replaced
     * @param _newMember - New member to be added
     * @return True if success
     */
    function replaceBoardMember(address _oldMember, address _newMember)
        public
        onlyBoard
        returns (bool)
    {
        require(removeBoardMember(_oldMember), "Failed to remove old member");
        return addBoardMember(_newMember);
    }

    /**
     * @dev Receive
     */
    receive() external payable {
        emit Deposit(msg.value);
    }
}
/**
 * @title MbdaBoard is the smart contract that will control all funds
 * */
function addBoardMember(address _newMember)
    public
    onlyBoard
    returns (bool)
{
    require(!boardMembers[_newMember], "Duplicate Board Member sent");
    boardMembersList.push(_newMember);
    boardMembers[_newMember] = true;
    if (boardMembersList.length > 1 && minVotes == 0) {
        minVotes = 1;
    }
    return true;
}

/**
 * @dev Adds a board member (onlyBoard)
 * @param _newMember - New member to be added
 * @return True if success
 */