Dao.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Governor.sol";

contract Dao is Governor {
    string private _name;

    event Propose(bytes32 indexed proposalId, address indexed proposer, bytes32 docHash);

    constructor(string memory gname, address core) Governor(core) {
        _name = gname;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function setRule(uint256 delay, uint256 rgstPeriod, uint256 votePeriod, uint256 snapTime, uint256 timelock, uint256 turnout, uint256 quorum) public onlyOwner {
        setRule(address(this), delay, rgstPeriod, votePeriod, snapTime, timelock, turnout, quorum);
    }

    function propose(address target, bytes memory calldatas, string memory uri) public onlyProposer() returns (bytes32) {
        bytes32 proposalId = propose(target, calldatas, uri, keccak256(bytes("")));
        return proposalId;   
    }
    function propose(address target, bytes memory calldatas, string memory uri, bytes32 docHash) public onlyProposer() returns (bytes32) {
        bytes32 proposalId = propose(msg.sender, target, calldatas, uri, docHash);

        emit Propose(proposalId, msg.sender, docHash);
        return proposalId;
    }

    function cancel(bytes32 proposalId) public onlyProposer returns (bool) {
        cancel(proposalId, msg.sender);
        return true;
    }

    function register(bytes32 proposalId, address delegatee) public returns (bool) {
        register(proposalId, msg.sender, delegatee);
        return true;
    }

    function snapBefore(bytes32 proposalId) public returns (bool) {
        ProposalState ps = snapBeforeVoting(proposalId);

        if(ps == ProposalState.Canceled) {
            return false;
        }

        return true;
    }

    function castVote(bytes32 proposalId, uint8 support) public returns (bool) {
        castVote(proposalId, msg.sender, support);
        return true;
    }

    function snapAfter(bytes32 proposalId) public returns (bool) {
        ProposalState ps = snapAfterVoting(proposalId);

        if(ps == ProposalState.Rejected) {
            return false;
        }

        return true;
    }

    function execute(bytes32 proposalId) public returns (bool) {
        executeProposal(proposalId);
        return true;
    }
}

Governor.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./core/Core.sol";
import "../library/Authority.sol";
import "../library/TypeCrypto.sol";
import "../token/IERC20.sol";

contract Governor is Authority, TypeCrypto {
    // contract core module
    Core _core;

    uint256 _nonce = 1;

    mapping(bytes32 => Proposal) _proposals;
    mapping(bytes32 => ProposalStatus) _status;
    mapping(bytes32 => uint256) _nonces;

    constructor(address core) {
        _setCore(Core(core));
    }

    function setCore(Core core) public onlyAdmin {
        _setCore(core);
    }
    function _setCore(Core core) private {
        _core = core;
    }

    function getProposalId(address proposer, address target, bytes memory calldatas, string memory uri, uint256 nonce) public pure returns (bytes32) {
        return keccak256(abi.encode(proposer, target, keccak256(calldatas), keccak256(bytes(uri)), nonce));
    }

    function setRule(address dao, uint256 nDelay, uint256 nRgstPeriod, uint256 nVotePeriod, uint256 nSnapTime, uint256 nTimelock, uint256 nTurnout, uint256 nQuorum) internal {
        // restricted min 60 sec
        require((nRgstPeriod > 60) && (nVotePeriod > 60) && (nSnapTime > 60), "Governor failed: restricted each time for more than 60 seconds");

        _core.setRule(dao, nDelay, nRgstPeriod, nVotePeriod, nSnapTime, nTimelock, nTurnout, nQuorum);
    }

    function getRule() public view returns (Rules memory) {
        return _core.getRuleByAccount(msg.sender);
    }

    function isContract(address _address) public view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(_address)
        }
        return size > 0;
    }

    function propose(address proposer, address target, bytes memory calldatas, string memory uri, bytes32 docHash) internal virtual returns (bytes32) {
        require(keccak256(bytes(uri)) != keccak256(bytes("")), "Governor failed: uri is empty");

        // check votingToken contract. address verification!
        require(isContract(target), "Governor failed: target isn't contract address");

        // get proposalId
        bytes32 _proposalId = getProposalId(proposer, target, calldatas, uri, _nonce);

        // check hold amount
        uint256 _threshold = _core.getThreshold();
        uint256 _balance = _core.getDefaultTokenAmount(proposer);
        require(_balance >= _threshold, "Governor failed: token balance is insufficient");

        // check propose-fee
        uint256 _fee = _core.getProposalFee();
        IERC20 _token = _core.getDefaultToken();
        bool success = _token.transferFrom(proposer, address(this), _fee);
        require(success, "Governor failed: proposal-fee must be approved");

        _propose(_proposalId, proposer, target, calldatas, uri, docHash);

        _nonces[_proposalId] = _nonce;
        _nonce++;

        return _proposalId;        
    }

    function _propose(bytes32 proposalId, address proposer, address target, bytes memory calldatas, string memory uri, bytes32 docHash) private {        
        // save vote rules
        _core.setRuleByProposal(address(this), proposalId);

        // save proposal_contents
        _proposals[proposalId] = Proposal(proposer, target, uri, docHash, calldatas);

        // save proposal_status
        Rules memory r = _core.getRuleByProposal(proposalId);

        uint256 _voteRegistration = block.timestamp + r.delay;
        uint256 _voteStart = _voteRegistration + r.registPeriod + r.snapTime;
        uint256 _voteEnd = _voteStart + r.votePeriod;

        _status[proposalId] = ProposalStatus(_voteRegistration, _voteStart, _voteEnd, ProposalState.Pending);

        _core.register(proposalId, proposer, proposer);
    }

    function cancel(bytes32 proposalId, address _proposer) internal {
        Proposal memory p = _proposals[proposalId];
        require(p.proposer == _proposer, "Governor failed: the caller isn't a proposer");

        ProposalState _state = getState(proposalId);
        require(_state == ProposalState.Pending || _state == ProposalState.Registration, "Governor failed: the vote is started already");

        _status[proposalId].state = ProposalState.Canceled;
    }

    function register(bytes32 proposalId, address delegator, address delegatee) internal {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");
        require(getState(proposalId) == ProposalState.Registration, "Governor failed: no registration period");
        
        _core.register(proposalId, delegator, delegatee);
    }

    /** @dev (just using interger but enum)
     * voting options
     * 1 : agree
     * 2 : disagree
     * 3 : abstain
     */
    function castVote(bytes32 proposalId, address voter, uint8 support) internal {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");
        require(getState(proposalId) == ProposalState.Active, "Governor failed: this proposal isn't active");

        _core.castVote(proposalId, voter, support);
    }

    function executeProposal(bytes32 proposalId) internal {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");
        require(getState(proposalId) == ProposalState.Completed, "Governor failed: this proposal isn't completed");

        uint256 _end = _status[proposalId].voteEnd;
        uint256 _lock = _core.getRuleByProposal(proposalId).timelock;
        require(block.timestamp >= _end + _lock, "Governor failed: the waiting time for execution has not passed yet");

        Proposal memory p = _proposals[proposalId];
        (bool success, ) = p.target.call(p.calldatas);
        require(success, "Governor failed: can't execute proposal");

        _status[proposalId].state = ProposalState.Executed;
    }

    function getProposal(bytes32 proposalId) public view returns (Proposal memory) {
        return _proposals[proposalId];
    }

    function getPower(bytes32 proposalId, address voter) public view returns (uint256) {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");

        uint256 _power = _core.getPower(proposalId, voter);
        return _power;
    }

    // All users can't see the voting result when the vote is in progress.
    function getResult(bytes32 proposalId) public view returns (uint256[] memory) {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");
        require(getState(proposalId) != ProposalState.Canceled, "Governor failed: this proposal is canceled");

        uint256 _voteEnd = _status[proposalId].voteEnd;
        require(block.timestamp >= _voteEnd, "Governor failed: can't check voting result yet");

        uint256[] memory v = _core.getResult(proposalId);

        return v;
    }

    function getState(bytes32 proposalId) public view returns (ProposalState) {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");

        uint256 currentTime = block.timestamp;

        ProposalStatus memory ps = _status[proposalId];

        // canceled or succeeded or defeated or executed
        if(
            ps.state == ProposalState.Canceled  ||
            ps.state == ProposalState.Completed ||
            ps.state == ProposalState.Rejected  ||
            ps.state == ProposalState.Executed)
        {
            return ps.state;
        }

        // pending
        if(currentTime < ps.voteRegistration){
            return ProposalState.Pending;
        }

        // registration
        Rules memory r = _core.getRuleByProposal(proposalId);
        if(currentTime < ps.voteRegistration + r.registPeriod){
            return ProposalState.Registration;
        }

        // front snap (using front-snap)
        if(currentTime < ps.voteStart){
            return ProposalState.FrontSnap;
        }

        // voting active
        if(currentTime <= ps.voteEnd && ps.state == ProposalState.FrontSnap){
            return ProposalState.Active;
        }

        // back snap
        if(currentTime > ps.voteEnd && ps.state == ProposalState.FrontSnap){
            return ProposalState.BackSnap;
        }

        // canceled because proposer didn't snap within the period
        return ProposalState.Canceled;
    }

    function snapBeforeVoting(bytes32 proposalId) internal returns (ProposalState) {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");
        require(getState(proposalId) == ProposalState.FrontSnap, "Governor failed: no snap period");

        bool success = _core.snapBefore(proposalId);

        if (success) {
            _status[proposalId].state = ProposalState.FrontSnap;
        } else {
            _status[proposalId].state = ProposalState.Canceled;
        }

        return _status[proposalId].state;
    }

    function snapAfterVoting(bytes32 proposalId) internal returns (ProposalState) {
        require(_nonces[proposalId] != 0, "Governor failed: can't find proposal ID");
        require(getState(proposalId) == ProposalState.BackSnap, "Governor failed: no snap period");

        bool success = _core.snapAfter(proposalId);

        if(success) {
            _status[proposalId].state = ProposalState.Completed;
        } else {
            _status[proposalId].state = ProposalState.Rejected;
        }

        return _status[proposalId].state;
    }
}