Core.sol

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

import "./Register.sol";
import "./Snap.sol";
import "./Vote.sol";
import "../../setting/Rule.sol";
import "../../library/Authority.sol";

contract Core is Register, Snap, Vote, Rule, Authority {

    constructor(address tokenAddress, uint256 threshold, uint256 fee)
        Vote(tokenAddress)
        Rule(threshold, fee)
        Authority()
    {}

    function setDefaultToken(address newTokenAddress) public override onlyOwner {
        super.setDefaultToken(newTokenAddress);
    }

    function setThreshold(uint256 threshold) public override onlyOwner {
        super.setThreshold(threshold);
    }

    function setProposalFee(uint256 fee) public override onlyOwner {
        super.setProposalFee(fee);
    }

    function setRule(address dao, uint256 nDelay, uint256 nRgstPeriod, uint256 nVotePeriod, uint256 nSnapTime, uint256 nTimelock, uint256 nTurnout, uint256 nQuorum)
        public override onlyOwner returns (bool)
    {
        bool success = super.setRule(dao, nDelay, nRgstPeriod, nVotePeriod, nSnapTime, nTimelock, nTurnout, nQuorum);
        return success;
    }

    function setRuleByProposal(address proposer, bytes32 proposalId) public override onlyOwner {
        super.setRuleByProposal(proposer, proposalId);
    }

    function register(bytes32 proposalId, address delegator, address delegatee) public onlyOwner {
        require(getDefaultTokenAmount(delegator) > 0, "Core failed: not enough token balance");

        bool success = registerRights(proposalId, delegator, delegatee);
        require(success, "Core failed: Can't register voting right");
    }

    function castVote(bytes32 proposalId, address voter, uint8 support) public override onlyOwner returns (bool) {
        require(support > 0 && support <= 3, "Core failed: the number is great than allowable value or zero");

        // Only those who have registered voting-power for themselves can participate in the vote.
        require(voter == getRegister(proposalId, voter), "Core failed: an registered account");

        bool success = super.castVote(proposalId, voter, support);
        return success;
    }

    function snapBefore(bytes32 proposalId) public onlyOwner returns (bool) {
        address[] memory voters = getAllRegisters(proposalId);

        uint256 _totalPower = 0;
        uint256 countVoters = voters.length;
        for( uint256 i = 0 ; i < countVoters ; i++ ){
            address _voter = voters[i];
            uint256 _power = getDefaultTokenAmount(_voter);
            setSnapBefore(proposalId, _voter, _power);
            _totalPower += _power;
        }

        uint256 _totalSupply = getTotalSupply();
        uint256 _registeredTurnout = _totalPower * 100 / _totalSupply;
        uint256 _turnout = getRuleByProposal(proposalId).turnout;
        
        // less than turnout
        if(_registeredTurnout < _turnout){
            return false;
        }

        return true;               
    }

    function snapAfter(bytes32 proposalId) public onlyOwner returns (bool) {
        address[] memory voters = getAllRegisters(proposalId);

        // total turnout
        uint256 _totalTurnout = 0;

        /** @dev
         *  It takes the amount of voting rights from the list and stores the min value in _powerAfter of Snap.sol,
         *  compared to the amount of tokens it currently holds.
         */
        uint256 countVoters = voters.length;
        uint256 maxAccumulatedValue = 0;
        for( uint256 i = 0 ; i < countVoters ; i++ ){
            address _delegator = voters[i];
            address _delegatee = getRegister(proposalId, _delegator);

            // If the delegated account did not participate in the vote
            if(!hasVoted(proposalId, _delegatee)) continue;

            uint256 _curPower = getDefaultTokenAmount(_delegator);
            uint256 _prePower = getPower(proposalId, _delegator);

            // compare.(min value)
            if(_curPower > _prePower) {
                _curPower = _prePower;
            }

            setSnapAfter(proposalId, _delegator, _curPower);
            _totalTurnout += _curPower;

            // adjustment of voting power
            uint8 _support = getSupport(proposalId, _delegatee);
            uint256 _accumulated = setResult(proposalId, _support, _curPower);

            if(_support == 1) {
                maxAccumulatedValue = _accumulated;
            }
        }

        // compare to turnout
        uint256 _totalSupply = getTotalSupply();
        uint256 _votingTurnout = _totalTurnout * 100 / _totalSupply;
        uint256 _minTurnout = getRuleByProposal(proposalId).turnout;
        
        if(_votingTurnout < _minTurnout) {
            return false;
        }

        // compare to quorum
        // search a option that satisfies the quorum
        uint256 _maxRate = maxAccumulatedValue * 100 / _totalTurnout;
        uint256 _minQuorum = getRuleByProposal(proposalId).quorum;

        if(_maxRate < _minQuorum) {
            return false;
        }

        return true;
    }
}

Register.sol

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

contract Register {
    mapping(bytes32 => mapping(address => address)) _delegations;
    mapping(bytes32 => address[]) _registers;

    function getAllRegisters(bytes32 proposalId) public view returns (address[] memory) {
        return _registers[proposalId];
    }

    function hasRegistered(bytes32 proposalId, address voter) public view returns (bool) {
        return _delegations[proposalId][voter] != address(0x0);
    }

    function getRegister(bytes32 proposalId, address voter) public view returns (address) {
        return _delegations[proposalId][voter];
    }

    function registerRights(bytes32 proposalId, address delegator, address delegatee) internal returns (bool) {
        require(!hasRegistered(proposalId, delegator), "Register failed: has registered already");

        _delegations[proposalId][delegator] = delegatee;
        _registers[proposalId].push(delegator);
        return true;
    }
}

Vote.sol

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

import "../../token/IERC20.sol";

contract Vote {
    IERC20 _origin;

    mapping(bytes32 => mapping(uint8 => uint256)) _result;
    mapping(bytes32 => mapping(address => uint8)) _supports;
    
    constructor(address tokenAddress){
        _setDefaultToken(IERC20(tokenAddress));
    }

    function setDefaultToken(address newToken) public virtual {
        require(newToken != address(0x0), "Vote failed: This is zero address");
        _setDefaultToken(IERC20(newToken));
    }

    function _setDefaultToken(IERC20 newToken) private {
        _origin = newToken;
    }

    function getDefaultToken() public view returns (IERC20) {
        return _origin;
    }

    function getDefaultTokenAmount(address account) public view returns (uint256) {
        return _origin.balanceOf(account);
    }

    function getTotalSupply() public view returns (uint256) {
        return _origin.totalSupply();
    }

    function hasVoted(bytes32 proposalId, address voter) public view returns (bool) {
        return _supports[proposalId][voter] != 0;
    }

    function castVote(bytes32 proposalId, address voter, uint8 support) public virtual returns (bool) {
        require(!hasVoted(proposalId, voter), "Vote failed: has voted already");

        _supports[proposalId][voter] = support;
     
        return true;
    }

    function getSupport(bytes32 proposalId, address voter) public view returns (uint8) {
        require(_supports[proposalId][voter] != 0, "Vote failed: has not voted yet");

        return _supports[proposalId][voter];
    }

    function setResult(bytes32 proposalId, uint8 support, uint256 power) internal returns (uint256) {
        _result[proposalId][support] += power;
        return _result[proposalId][support];
    }

    function getResult(bytes32 proposalId) public view returns (uint256[] memory) {
        /** @dev (just using interger but enum)
         * voting options
         * 1 : agree
         * 2 : disagree
         * 3 : abstain
         */
        uint256[] memory v = new uint256[](4);

        // The data of index '0' is dummy
        for(uint8 i = 1 ; i <= 4 ; i++ ){
            v[i] = _result[proposalId][i];
        }

        return v;
    }
}

Snap.sol

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

contract Snap {
    mapping(bytes32 => mapping(address => uint256)) _powerBefore;
    mapping(bytes32 => mapping(address => uint256)) _powerAfter;

    function setSnapBefore(bytes32 proposalId, address voter, uint256 power) internal {
        _powerBefore[proposalId][voter] = power;
    }

    function setSnapAfter(bytes32 proposalId, address voter, uint256 power) internal {
        _powerAfter[proposalId][voter] = power;
    }

    function getPower(bytes32 proposalId, address voter) public view returns (uint256) {
        if(_powerAfter[proposalId][voter] != 0){
            return _powerAfter[proposalId][voter];
        }
        return _powerBefore[proposalId][voter];
    }
}

Rule.sol

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

import "../library/TypeStruct.sol";

contract Rule is TypeStruct {
    uint256 _threshold;
    uint256 _proposalFee;

    mapping(address => Rules) _userRules;
    mapping(bytes32 => Rules) _proposalRules;
    mapping(address => bool) _isInitialized;

    constructor(uint256 threshold, uint256 fee) {
        _setThreshold(threshold);
        _setProposalFee(fee);
    }

    function setThreshold(uint256 threshold) public virtual {
        _setThreshold(threshold);
    }
    function _setThreshold(uint256 threshold) private {
        _threshold = threshold;
    }
    function getThreshold() public view returns (uint256) {
        return _threshold;
    }

    function setProposalFee(uint256 fee) public virtual {
        _setProposalFee(fee);
    }
    function _setProposalFee(uint256 fee) private {
        _proposalFee = fee;
    }
    function getProposalFee() public view returns (uint256) {
        return _proposalFee;
    }

    function setRule(address proposer, uint256 nDelay, uint256 nRgstPeriod, uint256 nVotePeriod, uint256 nSnapTime, uint256 nTimelock, uint256 nTurnout, uint256 nQuorum)
        public virtual returns (bool)
    {
        Rules memory r = Rules(nDelay, nRgstPeriod, nVotePeriod, nSnapTime, nTimelock, nTurnout, nQuorum);
        _userRules[proposer] = r;

        if (!_isInitialized[proposer]) {
            _isInitialized[proposer] = true;
        }
        return true;
    }

    function setRuleByProposal(address proposer, bytes32 proposalId) public virtual {
        require(_isInitialized[proposer], "Rule failed: have to initialize user rules");

        Rules memory r = getRuleByAccount(proposer);
        _proposalRules[proposalId] = r;
    }

    function getRuleByAccount(address account) public view returns (Rules memory) {
        return _userRules[account];
    }

    function getRuleByProposal(bytes32 proposalId) public view returns (Rules memory) {
        return _proposalRules[proposalId];
    }
}