// File: contracts/teamVault/teamVault.sol



// SPDX-License-Identifier: MIT
// XEN Contracts v0.6.0
pragma solidity ^0.8.13;

import {CQuorum}                                from './dao/quorum.sol';

contract CTeamVault is CQuorum {

    constructor() {
    }

    modifier onlyInterCall() {
        require( address(this) == msg.sender, "onlyInterCall: caller is not this");
        _;
    }

    function approval( uint256 docId ) public onlyMember noReentrancy {
        _approval( docId );
    }

    function proposalRecommend( address newMember ) public onlyMember noReentrancy returns( uint256 ) {
        require( _memberIndex.getIndex( newMember ) == 0, "proposalRecommend: already a member" );
        bytes memory param      = abi.encode( newMember );
        bytes memory packed     = abi.encode( "recommend(address)", param );
        return _proposal( address(this), packed );
    }
 
    function proposalExpulsion( address member ) public onlyMember noReentrancy returns( uint256 ) {
        require( _memberIndex.getIndexedSize() > 2, "proposalExpulsion: must have at least 3 members" );
        require( _memberIndex.getIndex( member ) > 0, "proposalExpulsion: not a member" );
        require( _creator != member, "proposalExpulsion: Can't banish creator");
        bytes memory param      = abi.encode( member );
        bytes memory packed     = abi.encode( "expulsion(address)", param );
        return _proposal( address(this), packed );
    }

    function proposalTransfer20( address toContract, address to, uint256 amount ) public onlyMember noReentrancy returns( uint256 ) {
        bytes memory param      = abi.encode( to, amount );
        bytes memory packed     = abi.encode( "transfer(address,uint256)", param );
        return _proposal( toContract, packed );           
    }     

    function proposalTransfer721( address toContract, address to, uint256 tokenId ) public onlyMember noReentrancy returns( uint256 ) {
        bytes memory param      = abi.encode( address(this), to, tokenId );
        bytes memory packed     = abi.encode( "transferFrom(address,address,uint256)", param );
        return _proposal( toContract, packed );           
    }      

    function proposalTransfer1155( address toContract, address to, uint256 tokenId, uint256 amount ) public onlyMember noReentrancy returns( uint256 ) {
        bytes memory param      = abi.encode( address(this), to, tokenId, amount, "" );
        bytes memory packed     = abi.encode( "safeTransferFrom(address,address,uint256,uint256,bytes)", param );
        return _proposal( toContract, packed );           
    }    

    //ERC20.ERC721
    function proposalApprove( address toContract, address spender, uint256 value ) public onlyMember noReentrancy returns( uint256 ) {
        bytes memory param      = abi.encode( spender, value );
        bytes memory packed     = abi.encode( "approve(address,uint256)", param );
        return _proposal( toContract, packed );           
    }       

    function proposal( address toContract, string calldata funcSignatrue, bytes calldata funcParams ) public onlyMember noReentrancy returns( uint256 ) {
        bytes memory packed     = abi.encode( funcSignatrue, funcParams );
        return _proposal( toContract, packed );       
    }

    ////////////////////////////////////////////////////////////////////////////////
    //invoke funtion

    function recommend( address newMember ) public onlyInterCall {
        _addMember( newMember );
    }

    function expulsion( address member ) public onlyInterCall {
        _removeMember( member );
    }
 
}

// File: contracts/teamVault/dao/quorum.sol



// SPDX-License-Identifier: MIT
// XEN Contracts v0.6.0
pragma solidity ^0.8.13;

import {_CReentrancyGuards}                     from '../security/reentrancyGuards.sol';
import {CAddressIndexing}                       from '../index/addressIndex.sol';
import {IQuorum}                                from './iQuorum.sol';

contract CQuorum is _CReentrancyGuards, IQuorum {

    uint256                                    private                 _idTracker;
    uint256                                    internal                _expirationPeriod;   //안건 유효 기간 (오늘 포함 된 기간)
    uint256                                    internal                _minimumMembers;     //최소 구성원 수
    CAddressIndexing                           internal                _memberIndex;
    mapping( uint256 => TDraftDoc )            internal                _doc;
    address                                    internal                _creator;

    event eventAddMember( address newMember );
    event eventRemoveMember( address member );
    event eventProposal( address drafter, uint256 docId, address toContract, bytes callData, bool invokable );
    event eventApproval( address approvor, uint256 docId, bool invokable );
    event eventCancel( uint256 docId );
    event eventInvoke( address sender, uint256 docId, bytes returnData );

    constructor() {
        _memberIndex        = new CAddressIndexing();
        _idTracker          = 1;  
        _expirationPeriod   = 3;    //3 days
        _minimumMembers     = 2;    //2 명
        _creator            = msg.sender;
    }

    modifier onlyMember() {
        if( _memberIndex.getIndexedSize() < _minimumMembers ) { //최소 맴버 인원을 충족하지 못 할 경우 contract owner만 호출 가능하다
            require( msg.sender == _creator, "onlyMember: If less than the minimum number of members, caller must be a contract owner");
        } else {
            require( _memberIndex.getIndex( msg.sender ) > 0, "onlyMember: caller is not member");
        }
        _;
    }

    modifier onlyQuorum( uint256 docId ) {
        /*
            승인된 후 invoke 호출한적이 없어야 야 한다.
            기안작성자가 invoke를 호출 하여야 한다.
            기안작성일로 부터 3일 이내에 호출되어야 한다.
            2명 미만 인경우 contract 생성자만 호출 가능
        */
        require( !_doc[ docId ].cancellation, "onlyQuorum: cancelled");
        require( !_doc[ docId ].invoked, "onlyQuorum: already invoked");
        require( _doc[ docId ].drafter == msg.sender, "onlyQuorum: caller is not drafter");
        require( (_doc[ docId ].draftDate + ( _expirationPeriod * 86400) ) > block.timestamp, "onlyQuorum: approval period has expired" );

        uint256 numberOfMembers = _memberIndex.getIndexedSize();
        if( _minimumMembers == 0 || numberOfMembers < _minimumMembers ) { //최소 인원 이하이면 contract owner만 호출 가능하다
             require( msg.sender == _creator, "onlyMember: If less than the minimum number of members, caller must be a contract owner");
        } else { //최소 인원 이상인 경우
            uint256 minimumApproval = (numberOfMembers / 2) + 1; 
            require( _doc[ docId ].approval >= minimumApproval, "onlyQuorum: approval count must be at least (number of members / 2) + 1");
        } 
        _;
    }

    function _revertReason (bytes memory revertData) internal pure returns (string memory reason) {
        uint l = revertData.length;
        if (l < 68) return "";
        uint t;
        assembly {
            revertData := add (revertData, 4)
            t := mload (revertData)
            mstore (revertData, sub (l, 4))
        }
        reason = abi.decode (revertData, (string));
        assembly {
            mstore (revertData, t)
        }
    }
    
    function _isInvokable( uint256 docId ) internal view returns( bool ) {
        if( _doc[ docId ].invoked ) 
            return false;
        if( (_doc[ docId ].draftDate + ( _expirationPeriod * 86400) ) < block.timestamp)
            return false;

        uint256 numberOfMembers = _memberIndex.getIndexedSize();
        if( _minimumMembers == 0 || numberOfMembers < _minimumMembers ) {
            if( _creator != msg.sender ) {
               return false;
            }
        } else { //최소 인원 이상인 경우
            uint256 minimumApproval = (numberOfMembers / 2) + 1;
            if( _doc[ docId ].approval < minimumApproval ) {
                return false;
            }
        } 
        return true;
    }

    function cancel( uint256 docId ) public noReentrancy override {
        require( _doc[ docId ].drafter == msg.sender, "cancel: caller is not the drafter" );
        require( !_doc[ docId ].cancellation, "cancel: already cancelled");
        require( (_doc[ docId ].draftDate + ( _expirationPeriod * 86400) ) > block.timestamp, "cancel: approval period has expired" );
        require( !_doc[ docId ].invoked, "_approval: already invoked" );

        _doc[ docId ].cancellation = true;
        emit eventCancel( docId );
    }

    function invoke( uint256 docId ) public onlyQuorum(docId) noReentrancy override {
        (string memory func, bytes memory param )   = abi.decode( _doc[docId].callData, ( string, bytes ) );  
        bytes4          FUNC_SELECTOR               = bytes4( keccak256(bytes(func)) );
        bytes memory    packedData                  = abi.encodePacked( FUNC_SELECTOR, param );

        ( _doc[docId].success, _doc[docId].returnData) = address(_doc[ docId ].to).call( packedData );
        if( !_doc[docId].success ) {
            revert( _revertReason(_doc[docId].returnData) );
        }
        _doc[docId].invoked     = true;
        _doc[docId].invokeDate  = block.timestamp;
        emit eventInvoke( msg.sender, docId, _doc[docId].returnData );        
    }

    function inquery( uint256 docId ) public view override returns( TDrafeDocViewer memory doc ) {
        doc.drafter         = _doc[docId].drafter;
        doc.draftDate       = _doc[docId].draftDate;
        doc.approval        = _doc[docId].approval;
        doc.cancellation    = _doc[docId].cancellation; 
        doc.invoked         = _doc[docId].invoked; 
        doc.invokeDate      = _doc[docId].invokeDate;
        doc.to              = _doc[docId].to;
        return doc;
    }

    function inqueryApprover( uint256 docId ) public view override returns( TSigner[] memory approvers ) {
        uint8 approval = _doc[docId].approval;
        if( approval > 0 ) {
           approvers      = new TSigner[]( approval );
            uint8 i = 0;
            for( ; i<approval; i++ ) {
               approvers[i] = _doc[ docId ].signer[i];
            } 
        }
    }

    function inqueryCallData( uint256 docId ) public view override returns( address, string memory, bytes memory ) {
        (string memory func, bytes memory param )   = abi.decode( _doc[docId].callData, ( string, bytes ) );
        return ( _doc[docId].to, func, param );
    }    

    function inqueryReturnData( uint256 docId ) public view override returns( bool, bool, bytes memory ) {
        return ( _doc[docId].invoked, _doc[docId].success, _doc[docId].returnData );
    }    

    function inqueryMembers() public view override returns( address[] memory members) {
        uint256 size = _memberIndex.getIndexedSize();
        members = new address[](size);
        uint256 i = 0;
        for( ; i<size; i++ ) {
            members[i] = _memberIndex.getAddress( i + 1 );
        }
        return members;
    }

    function inqueryNumberOfMembers() public view override returns( uint256 ) {
        return _memberIndex.getIndexedSize();
    }    

    function inqueryLatestId() public view override returns( uint256 ) {    
        return _idTracker-1;
    }

    function _proposal( address toContract, bytes memory callData ) internal returns( uint256 ) {
        uint256 docId = _idTracker;
        _doc[ docId ].drafter        = msg.sender;
        _doc[ docId ].draftDate      = (block.timestamp / 86400 ) * 86400;
        
        TSigner memory tSigner;
        tSigner.approver            = msg.sender;
        tSigner.time                = block.timestamp;

        _doc[ docId ].indexing[msg.sender]              = _doc[ docId ].approval;
        _doc[ docId ].signer[_doc[ docId ].approval]    = tSigner;

        _doc[ docId ].to             = toContract;        
        _doc[ docId ].callData       = callData;

        _doc[ docId ].approval++;   //drafter는 기본으로 approve 한 상태로 간주 한다         
        _idTracker++;        
        emit eventProposal( msg.sender, docId, toContract, callData, _isInvokable( docId ) ) ;
        return docId;
    }

    function _approval( uint256 docId ) internal {
        /*
            기안자는 sign을 할 수 없다
            안건 상정 취소된 문건은 승인 불가 하다
            기안작성 날짜로 부터 금일 포함 _expirationPeriod 일 이내에 승인 하여야 한다.
            구성원만이 sign을 할 수 있다
            sign한 구성원은 중복 승인을 할 수 없다
            invoke된 기안은 sign 할 수 없다.
            drafter는 기본으로 approve 한 상태로 간주 한다.
        */
        require( _doc[ docId ].drafter != msg.sender, "_approval: the drafter and signer must be different" );
        require( !_doc[ docId ].cancellation, "onlyQuorum: cancelled");
        require( (_doc[ docId ].draftDate + ( _expirationPeriod * 86400) ) > block.timestamp, "_approval: approval period has expired" );
        require( _memberIndex.getIndex( msg.sender ) > 0, "_approval: caller is not member" );
        require( _doc[ docId ].indexing[msg.sender] == 0, "_approval: already approved" );
        require( !_doc[ docId ].invoked, "_approval: already invoked" );

        TSigner memory tSigner;
        tSigner.approver    = msg.sender;
        tSigner.time        = block.timestamp;

        _doc[ docId ].indexing[msg.sender]              = _doc[ docId ].approval;
        _doc[ docId ].signer[_doc[ docId ].approval]    = tSigner;
        _doc[ docId ].approval++;        
        emit eventApproval( msg.sender, docId, _isInvokable( docId ) );        
    }

    function _addMember( address newMember ) internal {
        _memberIndex.addIndex( newMember );
        emit eventAddMember( newMember ) ;
    }

    function _removeMember( address member ) internal {
        require( _memberIndex.getIndexedSize() > 2, "_removeMember: must have at least 3 members" );
        _memberIndex.removeIndex( member );
        emit eventRemoveMember( member ) ;
    }
}
// File: contracts/teamVault/dao/iQuorum.sol



// SPDX-License-Identifier: MIT
// XEN Contracts v0.6.0
pragma solidity ^0.8.13;


interface IQuorum {

struct TSigner {
    address                         approver;
    uint256                         time;
}

struct TDraftDoc {
    address                         drafter;        //기안자
    uint256                         draftDate;      //작성 날짜

    uint8                           approval;       //동의한 member 수
    mapping( uint8 => TSigner )     signer;         //동의한 account
    mapping( address => uint8 )     indexing;


    bool                            cancellation;   //안건 상정 취소
    bool                            invoked;        //transaction 호출 여부
    uint256                         invokeDate;
    address                         to;    
    bytes                           callData;
    bool                            success;
    bytes                           returnData;
}

struct TDrafeDocViewer {
    address                         drafter;        //기안자
    uint256                         draftDate;      //작성 날짜

    uint8                           approval;       //동의한 member 수
    bool                            cancellation;   //상정 취소 여부
    bool                            invoked;        //transaction 호출 여부
    uint256                         invokeDate;     //실행한 날짜
    address                         to;             //호출 할 contract address -> 0.6.0.2 이상 버전
}

    function invoke( uint256 docId ) external;
    function cancel( uint256 docId ) external;
    
    function inquery( uint256 docId ) external view returns( TDrafeDocViewer memory doc );
    function inqueryApprover( uint256 docId ) external view returns( TSigner[] memory approvers );
    function inqueryCallData( uint256 docId ) external view returns( address, string memory, bytes memory );
    function inqueryReturnData( uint256 docId ) external view returns( bool, bool, bytes memory );

    function inqueryMembers() external view returns( address[] memory members);
    function inqueryNumberOfMembers() external view returns( uint256 );
    function inqueryLatestId() external view returns( uint256 );

}

// File: contracts/teamVault/index/addressIndex.sol



// SPDX-License-Identifier: MIT
// XEN Contracts v0.6.0
pragma solidity ^0.8.13;

import {_CJeffBaseOwnerShip}                    from '../base/jeffBaseOwnerShip.sol';

//index 1 부터 시작 함
contract CAddressIndexing is _CJeffBaseOwnerShip {

    uint256                         internal _mIndexSize;
    mapping(address => uint256)     internal _mAddress;
    mapping(uint256 => address)     internal _mIndex;

    constructor() {
        _mIndexSize = 1;
    }

    function _addIndex( address account ) internal returns(bool res){
      assembly {
        function getSlotAddress(acc) -> a {
            mstore(0, acc)
            mstore(32, _mAddress.slot)
            a := keccak256(0, 64)
        }
        function getSlotIndex(idx) -> a {
            mstore(0, idx)
            mstore(32, _mIndex.slot)
            a := keccak256(0, 64)
        }            

        let slotAccount   := getSlotAddress( account )
        let slotIndexSize := _mIndexSize.slot

        let accountIndex  := sload(slotAccount) 
        let indexSize     := sload(slotIndexSize)

        if or( eq(indexSize, 1), iszero( accountIndex )) {
          let slotIndex := getSlotIndex(indexSize)
          sstore(slotAccount, indexSize)
          sstore(slotIndex, account)
          sstore(slotIndexSize, add(indexSize,1))
          res := 1
        }
      }
      return res;
    }    
    
    function _removeIndex( address account ) internal {
      assembly {
        function getSlotAddress(acc) -> a {
            mstore(0, acc)
            mstore(32, _mAddress.slot)
            a := keccak256(0, 64)
        }
        function getSlotIndex(idx) -> a {
            mstore(0, idx)
            mstore(32, _mIndex.slot)
            a := keccak256(0, 64)
        }        

        let slotIndexSize := _mIndexSize.slot
        let indexSize     := sload(slotIndexSize)

        let slotAccount   := getSlotAddress( account )
        let accountIndex  := sload(slotAccount) 

        let lastIdx       := sub( indexSize, 1 )
        let slotLastIndex := getSlotIndex(lastIdx)

        if gt( accountIndex, 0 ) {
          switch eq( lastIdx, accountIndex ) 
          case 0 {
            let slotIndex := getSlotIndex(accountIndex)
            let lastAddress := sload(slotLastIndex)
            sstore( slotIndex, lastAddress )
            
            let slotLastAddress := getSlotAddress( lastAddress )
            sstore( slotLastAddress, accountIndex )
          }
          sstore( slotAccount, 0 )
          sstore( slotLastIndex, 0 )
          sstore( slotIndexSize, lastIdx )
        }
      }
    }

    function _getIndex( address val ) internal view returns( uint256 ){
        return _mAddress[ val ];
    }

    //index: 1부터 시작
    function _getAddress( uint256 index ) internal view returns( address ){
        return _mIndex[index];
    }

    function _getIndexedSize() internal view returns( uint256 ) {
        return _mIndexSize -1;
    }

    ////////////////////////////////////////////////////////////////////////////////
    //public
    function addIndex( address val ) public onlyOwner returns(bool) {
        return _addIndex( val );
    }
    
    function removeIndex( address val ) public onlyOwner {
        _removeIndex( val );
    }

    function getIndex( address val ) public view returns( uint256 ){
        return _getIndex( val );
    }

    function getAddress( uint256 index ) public view returns( address ){
        return _getAddress( index );
    }

    function getIndexedSize() public view returns( uint256 ) {
        return _getIndexedSize();
    }

}
// File: contracts/teamVault/security/reentrancyGuards.sol



// SPDX-License-Identifier: MIT
// XEN Contracts v0.6.0
pragma solidity ^0.8.1;

contract _CReentrancyGuards {
    bool        private                 _bGuard;
    modifier noReentrancy() {
        require( !_bGuard, "Reentrant call" );
        _bGuard = true;
        _;
        _bGuard = false;
    }
}
// File: contracts/teamVault/base/jeffBaseOwnerShip.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/access/Ownable.sol";

/*
    Edition:
        V0.6.0.2    -   2023.08.30

*/
contract _CJeffBaseOwnerShip is Ownable {

    constructor() {
    }

    function _isContract( address a ) internal view returns(bool){
      uint32 size;
      assembly {
        size := extcodesize(a)
      }
      return (size > 0);
    }

    modifier onlyOwnerShip() {
        require( Ownable(msg.sender).owner() == owner(), "onlyOwnerShip: caller' owner is different" );
        _;
    }

    modifier onlyOwnerShipEx() {
        if( msg.sender != owner() ) {
            if( _isContract( msg.sender )) { // caller가 contract 이면
                try Ownable(msg.sender).owner() returns ( address res ) {
                    //호출한 contract의 owner와 this owner가 동일 해야 한다
                    require( res == owner(), "onlyOwnerShipEx: caller' owner is different" );
                } catch {
                    revert( "onlyOwnerShipEx: caller does not have ownable interface" );
                }
            } else { //caller가 account 이면
                revert( "onlyOwnerShipEx: caller is not owner" );
            }
        }
        _;
    }

    function owner() public view virtual override(Ownable) returns (address) {
        return Ownable.owner();
    }

}
// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}