// File: contracts/NNRewardPool.sol



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

pragma solidity ^0.6.12;

import "./lib/SafeMath.sol";
import "./iface/INestPool.sol";
import "./lib/SafeERC20.sol";
import './lib/TransferHelper.sol';
import "./iface/INestMining.sol";
import "./iface/INNRewardPool.sol";

/// @title NNRewardPool
/// @author Inf Loop - <inf-loop@nestprotocol.org>
/// @author Paradox  - <paradox@nestprotocol.org>

/// @notice The NNRewardPool contract distributes the mining rewards,
///     15% share of the amount of nest-token produced by miners
/// @dev The nest-tokens are put in NestPool. This contract only traces 
///     the sum-amount of all of the rewards (nest-token)
///   - NNToken is pre-deployed in Nest v3.0, so we should connect (legacy)
///       with NNRewardPool. Whenever a NN holder transfers NN token to another,
///       NNToken will call back NNRewardPool.nodeCount() to settle rewards (decisively)
///       for both sender and receiver.
///   - After upgrading, NNRewardPool will count rewards from zero. Any NN holder should
///       claim rewards that had been issued before upgrading from the old contract. Old
///       data about NN rewards will be dropped in this contract, while it can also accessible
///       through OLD (Nest v3.0) contracts.
contract NNRewardPool is INNRewardPool {
    using SafeMath for uint256;

    /* ========== STATE ============== */

    uint8   public flag;     // | 1: active 
                            // | 0: uninitialized
                            // | 2: shutdown

    uint8   constant NNREWARD_FLAG_UNINITIALIZED    = 0;
    uint8   constant NNREWARD_FLAG_ACTIVE           = 1;
    uint8   constant NNREWARD_FLAG_PAUSED           = 2;

    uint256 public rewardSum;
    uint256 public totalSupplyNN;

    /// @dev From nest-node address to checkpoints of reward-sum
    mapping(address => uint256) public rewardSumCheckpoint;

    /* ========== ADDRESSES ============== */

    address public C_NNToken;
    address public C_NestToken;
    address public C_NestPool;
    address public C_NestMining;

    address public governance;


    /* ========== CONSTRUCTOR ========== */

    /// @notice Constructor of NNRewardPool contract
    /// @dev The NNToken contract was created on the Ethereum mainnet 
    /// @param NestPool The address of NestPool Contract
    /// @param NNToken The address of NestNode Token Contract
    constructor(address NestPool, address NNToken) public
    {
        C_NestPool = NestPool;
        C_NNToken = NNToken;
        totalSupplyNN = uint128(ERC20(C_NNToken).totalSupply());
        governance = msg.sender;
        flag = NNREWARD_FLAG_UNINITIALIZED;
    }

    function start() external onlyGovernance
    {
        require(flag == NNREWARD_FLAG_UNINITIALIZED, "Nest:NTC:!flag");

        flag = NNREWARD_FLAG_ACTIVE;
    }

    /* ========== MODIFIERS ========== */

    modifier onlyBy(address _account)
    {
        require(msg.sender == _account, "Nest:NN:!Auth");
        _;
    }

    modifier noContract() 
    {
        require(address(msg.sender) == address(tx.origin), "Nest:NN:BAN(contract)");
        _;
    }

    modifier onlyGovernance() 
    {
        require(msg.sender == governance, "Nest:NN:!governance");
        _;
    }

    modifier onlyGovOrBy(address _account)
    {
        if (msg.sender != governance) { 
            require(msg.sender == _account,
                "Nest:NN:!Auth");
        }
        _;
    }

    /* ========== GOVERNANCE ========== */

    /// @dev To ensure that all of governance-addresses be consistent, every contract
    ///        besides NestPool must load newest `governance` from NestPool.
    function loadGovernance() override external 
    { 
        governance = INestPool(C_NestPool).governance();
    }

    /// @dev The function loads all nest-contracts, it is supposed to be called by NestPool
    function loadContracts() override external onlyGovOrBy(C_NestPool)
    {
        C_NestToken = INestPool(C_NestPool).addrOfNestToken();
        C_NNToken = INestPool(C_NestPool).addrOfNNToken();
        C_NestMining = INestPool(C_NestPool).addrOfNestMining();    

    }

    /// @dev Stop service for emergency
    function pause() external onlyGovernance
    {
        require(flag == NNREWARD_FLAG_ACTIVE, "Nest:NN:!flag");
        flag = NNREWARD_FLAG_PAUSED;
        emit FlagSet(address(msg.sender), uint256(NNREWARD_FLAG_PAUSED));
    }

    /// @dev Resume service 
    function resume() external onlyGovernance
    {
        require(flag == NNREWARD_FLAG_PAUSED, "Nest:NN:!flag");
        flag = NNREWARD_FLAG_ACTIVE;
        emit FlagSet(address(msg.sender), uint256(NNREWARD_FLAG_ACTIVE));
    }

    /* ========== ADDING REWARDS ========== */


    /// @notice Add rewards for Nest-Nodes, only NestMining (contract) are allowed
    /// @dev  The rewards need to pull from NestPool
    /// @param _amount The amount of Nest token as the rewards to each nest-node
    function addNNReward(uint256 _amount) override external onlyBy(C_NestMining)
    {
        if (_amount > 0) {
            uint256 _newSum = uint256(rewardSum).add(_amount);
            rewardSum = uint128(_newSum);
            emit NNRewardAdded(_amount, _newSum);
        }
        return;
    }

    // /// @dev The updator is to update the sum of NEST tokens mined in NestMining
    // function updateNNReward() external
    // {
    //     require(flag == NNREWARD_FLAG_ACTIVE, "Nest:NN:!flag");

    //     uint256 _allMined = INestMining(C_NestMining).minedNestAmount();
    //     if (_allMined > rewardSum) {
    //         uint256 _amount = _allMined.mul(NN_REWARD_PERCENTAGE).div(100).sub(rewardSum);
    //         uint256 _newSum = uint256(rewardSum).add(_amount);
    //         rewardSum = uint128(_newSum);
    //         emit NNRewardAdded(_amount, _newSum);
    //     }
    // }

    // modifier updateNNReward1()
    // {
    //     require(flag == NNREWARD_FLAG_ACTIVE, "Nest:NN:!flag");

    //     uint256 _allMined = INestMining(C_NestMining).minedNestAmount();
    //     if (_allMined > rewardSum) {
    //         uint256 _amount = _allMined.mul(NN_REWARD_PERCENTAGE).div(100).sub(rewardSum);
    //         uint256 _newSum = uint256(rewardSum).add(_amount);
    //         rewardSum = uint128(_newSum);
    //         emit NNRewardAdded(_amount, _newSum);
    //     }
    //    _;
    // }

    /* ========== CLAIM/SETTLEMENT ========== */

    /// @notice Claim rewards by Nest-Nodes
    /// @dev The rewards need to pull from NestPool
    function claimNNReward() override external noContract 
    {
        require(flag == NNREWARD_FLAG_ACTIVE, "Nest:NN:!flag");

        uint256 blnc =  ERC20(C_NNToken).balanceOf(address(msg.sender));
        require(blnc > 0, "Nest:NN:!(NNToken)");
        uint256 total = totalSupplyNN;
        uint256 sum = rewardSum;
        uint256 reward = sum.sub(rewardSumCheckpoint[address(msg.sender)]);
        uint256 share = reward.mul(blnc).div(total);

        rewardSumCheckpoint[address(msg.sender)] = sum;
        emit NNRewardClaimed(address(msg.sender), share);
     
        INestPool(C_NestPool).withdrawNest(address(this), share);
        require(ERC20(C_NestToken).transfer(address(msg.sender), share), "Nest:NN:!TRANS");
        
        return;
    }

    /// @notice Settle rewards for two NN holders
    /// @dev The function is for callback from NNToken. It is banned for contracts.
    /// @param from The address of the NN sender 
    /// @param to The address of the NN receiver 
    function settleNNReward(address from, address to) internal
    {
        require(flag == NNREWARD_FLAG_ACTIVE, "Nest:NN:!flag");

        uint256 fromBlnc = ERC20(C_NNToken).balanceOf(address(from));
        require (fromBlnc > 0, "Nest:NN:!(fromBlnc)");
        uint256 sum = rewardSum;
        uint256 total = totalSupplyNN;

        uint256 fromReward = sum.sub(rewardSumCheckpoint[from]).mul(fromBlnc).div(total);      
        rewardSumCheckpoint[from] = sum;      
       
        uint256 toBlnc = ERC20(C_NNToken).balanceOf(address(to));
        uint256 toReward = sum.sub(rewardSumCheckpoint[to]).mul(toBlnc).div(total);
        rewardSumCheckpoint[to] = sum;
        
        if (fromReward > 0) {
            INestPool(C_NestPool).withdrawNest(address(this), fromReward);
            require(ERC20(C_NestToken).transfer(from, fromReward), "Nest:NN:!TRANS");
            emit NNRewardClaimed(from, uint128(fromReward));
        }

        if (toReward > 0) { 
            INestPool(C_NestPool).withdrawNest(address(this), toReward);
            require(ERC20(C_NestToken).transfer(to, toReward), "Nest:NN:!TRANS");
            emit NNRewardClaimed(to, uint128(toReward));
        }

        return;
    }

    /// @dev The callback function called by NNToken.transfer()
    /// @param fromAdd The address of 'from' to transfer
    /// @param toAdd The address of 'to' to transfer
    function nodeCount(address fromAdd, address toAdd) 
        override
        external
        onlyBy(address(C_NNToken)) 
    {
        settleNNReward(fromAdd, toAdd);
        return;
    }

    /// @notice Show the amount of rewards unclaimed
    /// @return reward The reward of a NN holder
    function unclaimedNNReward() override external view returns (uint256 reward) 
    {
        uint256 blnc = ERC20(C_NNToken).balanceOf(address(msg.sender));
        uint256 sum = uint256(rewardSum);
        uint256 total = uint256(totalSupplyNN);
     
        reward = sum.sub(rewardSumCheckpoint[address(msg.sender)]).mul(blnc).div(total);
    }

}
// File: contracts/lib/SafeMath.sol



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

pragma solidity ^0.6.12;

// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)

library SafeMath {
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x, 'ds-math-add-overflow');
    }

    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x, 'ds-math-sub-underflow');
    }

    function mul(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
    }

    function div(uint x, uint y) internal pure returns (uint z) {
        require(y > 0, "ds-math-div-zero");
        z = x / y;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    }
}
// File: contracts/iface/INestPool.sol



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

pragma solidity ^0.6.12;

import "../lib/SafeERC20.sol";

interface INestPool {

    // function getNTokenFromToken(address token) view external returns (address);
    // function setNTokenToToken(address token, address ntoken) external; 

    function addNest(address miner, uint256 amount) external;
    function addNToken(address contributor, address ntoken, uint256 amount) external;

    function depositEth(address miner) external payable;
    function depositNToken(address miner,  address from, address ntoken, uint256 amount) external;

    function freezeEth(address miner, uint256 ethAmount) external; 
    function unfreezeEth(address miner, uint256 ethAmount) external;

    function freezeNest(address miner, uint256 nestAmount) external;
    function unfreezeNest(address miner, uint256 nestAmount) external;

    function freezeToken(address miner, address token, uint256 tokenAmount) external; 
    function unfreezeToken(address miner, address token, uint256 tokenAmount) external;

    function freezeEthAndToken(address miner, uint256 ethAmount, address token, uint256 tokenAmount) external;
    function unfreezeEthAndToken(address miner, uint256 ethAmount, address token, uint256 tokenAmount) external;

    function getNTokenFromToken(address token) external view returns (address); 
    function setNTokenToToken(address token, address ntoken) external; 

    function withdrawEth(address miner, uint256 ethAmount) external;
    function withdrawToken(address miner, address token, uint256 tokenAmount) external;

    function withdrawNest(address miner, uint256 amount) external;
    function withdrawEthAndToken(address miner, uint256 ethAmount, address token, uint256 tokenAmount) external;
    // function withdrawNToken(address miner, address ntoken, uint256 amount) external;
    function withdrawNTokenAndTransfer(address miner, address ntoken, uint256 amount, address to) external;


    function balanceOfNestInPool(address miner) external view returns (uint256);
    function balanceOfEthInPool(address miner) external view returns (uint256);
    function balanceOfTokenInPool(address miner, address token)  external view returns (uint256);

    function addrOfNestToken() external view returns (address);
    function addrOfNestMining() external view returns (address);
    function addrOfNTokenController() external view returns (address);
    function addrOfNNRewardPool() external view returns (address);
    function addrOfNNToken() external view returns (address);
    function addrOfNestStaking() external view returns (address);
    function addrOfNestQuery() external view returns (address);
    function addrOfNestDAO() external view returns (address);

    function addressOfBurnedNest() external view returns (address);

    function setGovernance(address _gov) external; 
    function governance() external view returns(address);
    function initNestLedger(uint256 amount) external;
    function drainNest(address to, uint256 amount, address gov) external;

}
// File: contracts/lib/SafeERC20.sol



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

pragma solidity 0.6.12;

import "./Address.sol";
import "./SafeMath.sol";

library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(ERC20 token, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(ERC20 token, address from, address to, uint256 value) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(ERC20 token, address spender, uint256 value) internal {
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(ERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(ERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value);
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }
    function callOptionalReturn(ERC20 token, bytes memory data) private {
        require(address(token).isContract(), "SafeERC20: call to non-contract");
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

interface ERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: contracts/lib/TransferHelper.sol



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

pragma solidity ^0.6.12;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
    }

    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }

    function safeTransferFrom(address token, address from, address to, uint value) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
    }

    function safeTransferETH(address to, uint value) internal {
        (bool success,) = to.call{value:value}(new bytes(0));
        require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
    }
}
// File: contracts/iface/INestMining.sol



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

pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

import "../lib/SafeERC20.sol";


interface INestMining {
    
    struct Params {
        uint8    miningEthUnit;     // = 10;
        uint32   nestStakedNum1k;   // = 1;
        uint8    biteFeeRate;       // = 1; 
        uint8    miningFeeRate;     // = 10;
        uint8    priceDurationBlock; 
        uint8    maxBiteNestedLevel; // = 3;
        uint8    biteInflateFactor;
        uint8    biteNestInflateFactor;
    }

    function priceOf(address token) external view returns(uint256 ethAmount, uint256 tokenAmount, uint256 bn);
    
    function priceListOfToken(address token, uint8 num) external view returns(uint128[] memory data, uint256 bn);

    // function priceOfTokenAtHeight(address token, uint64 atHeight) external view returns(uint256 ethAmount, uint256 tokenAmount, uint64 bn);

    function latestPriceOf(address token) external view returns (uint256 ethAmount, uint256 tokenAmount, uint256 bn);

    function priceAvgAndSigmaOf(address token) 
        external view returns (uint128, uint128, int128, uint32);

    function minedNestAmount() external view returns (uint256);

    /// @dev Only for governance
    function loadContracts() external; 
    
    function loadGovernance() external;

    function upgrade() external;

    function setup(uint32   genesisBlockNumber, uint128  latestMiningHeight, uint128  minedNestTotalAmount, Params calldata initParams) external;

    function setParams1(uint128  latestMiningHeight, uint128  minedNestTotalAmount) external;
}
// File: contracts/iface/INNRewardPool.sol



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

pragma solidity ^0.6.12;

/// @title NNRewardPool
/// @author Inf Loop - <inf-loop@nestprotocol.org>
/// @author Paradox  - <paradox@nestprotocol.org>

interface INNRewardPool {
    
    /* [DEPRECATED]
        uint256 constant DEV_REWARD_PERCENTAGE   = 5;
        uint256 constant NN_REWARD_PERCENTAGE    = 15;
        uint256 constant MINER_REWARD_PERCENTAGE = 80;
    */

    /// @notice Add rewards for Nest-Nodes, only governance or NestMining (contract) are allowed
    /// @dev  The rewards need to pull from NestPool
    /// @param _amount The amount of Nest token as the rewards to each nest-node
    function addNNReward(uint256 _amount) external;

    /// @notice Claim rewards by Nest-Nodes
    /// @dev The rewards need to pull from NestPool
    function claimNNReward() external ;  

    /// @dev The callback function called by NNToken.transfer()
    /// @param fromAdd The address of 'from' to transfer
    /// @param toAdd The address of 'to' to transfer
    function nodeCount(address fromAdd, address toAdd) external;

    /// @notice Show the amount of rewards unclaimed
    /// @return reward The reward of a NN holder
    function unclaimedNNReward() external view returns (uint256 reward);

    /// @dev Only for governance
    function loadContracts() external; 

    /// @dev Only for governance
    function loadGovernance() external; 

    /* ========== EVENTS ============== */

    /// @notice When rewards are added to the pool
    /// @param reward The amount of Nest Token
    /// @param allRewards The snapshot of all rewards accumulated
    event NNRewardAdded(uint256 reward, uint256 allRewards);

    /// @notice When rewards are claimed by nodes 
    /// @param nnode The address of the nest node
    /// @param share The amount of Nest Token claimed by the nest node
    event NNRewardClaimed(address nnode, uint256 share);

    /// @notice When flag of state is set by governance 
    /// @param gov The address of the governance
    /// @param flag The value of the new flag
    event FlagSet(address gov, uint256 flag);
}
// File: contracts/lib/Address.sol



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

pragma solidity 0.6.12;

library Address {
    function isContract(address account) internal view returns (bool) {
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");
        (bool success, ) = recipient.call{value:amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
}