ETH Price: $2,503.73 (-0.84%)

Contract Diff Checker

Contract Name:
Distributor

Contract Source Code:

File 1 of 1 : Distributor

/**
 * ██████  ███████ ██    ██  ██████  ██      ██    ██ ███████ ██  ██████  ███    ██
 * ██   ██ ██      ██    ██ ██    ██ ██      ██    ██     ██  ██ ██    ██ ████   ██
 * ██████  █████   ██    ██ ██    ██ ██      ██    ██   ██    ██ ██    ██ ██ ██  ██
 * ██   ██ ██       ██  ██  ██    ██ ██      ██    ██  ██     ██ ██    ██ ██  ██ ██
 * ██   ██ ███████   ████    ██████  ███████  ██████  ███████ ██  ██████  ██   ████
 * 
 * @title BRO Revenue Share
 * 
 * @notice This is a smart contract developed by Revoluzion for BRO Revenue Share.
 * 
 * @dev This smart contract was developed based on the general
 * OpenZeppelin Contracts guidelines where functions revert instead of
 * returning `false` on failure. 
 * 
 * @author Revoluzion Ecosystem
 * @custom:email [email protected]
 * @custom:telegram https://t.me/RevoluzionEcosystem
 * @custom:website https://revoluzion.io
 * @custom:dapp https://revoluzion.app
 */
 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

/********************************************************************************************
  LIBRARY
********************************************************************************************/

/**
 * @title Address Library
 *
 * @notice Collection of functions providing utility for interacting with addresses.
 */
library Address {

    // ERROR

    /**
     * @notice Error indicating insufficient balance while performing an operation.
     *
     * @param account Address where the balance is insufficient.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @notice Error indicating an attempt to interact with a contract having empty code.
     *
     * @param target Address of the contract with empty code.
     */
    error AddressEmptyCode(address target);

    /**
     * @notice Error indicating a failed internal call.
     */
    error FailedInnerCall();

    // FUNCTION

    /**
     * @notice Calls a function on a specified address without transferring value.
     *
     * @param target Address on which the function will be called.
     * @param data Encoded data of the function call.
     *
     * @return returndata Result of the function call.
     *
     * @dev The `target` must be a contract address and this function must be calling
     * `target` with `data` not reverting.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @notice Calls a function on a specified address with a specified value.
     *
     * @param target Address on which the function will be called.
     * @param data Encoded data of the function call.
     * @param value Value to be sent in the call.
     *
     * @return returndata Result of the function call.
     *
     * @dev This function ensure that the calling contract actually have Ether balance
     * of at least `value` and that the called Solidity function is a `payable`. Should
     * throw if caller does have insufficient balance.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @notice Verifies the result of a function call and handles errors if any.
     *
     * @param target Address on which the function was called.
     * @param success Boolean indicating the success of the function call.
     * @param returndata Result data of the function call.
     *
     * @return Result of the function call or reverts with an appropriate error.
     *
     * @dev This help to verify that a low level call to smart-contract was successful
     * and will reverts if the target was not a contract. For unsuccessful call, this
     * will bubble up the revert reason (falling back to {FailedInnerCall}). Should
     * throw if both the returndata and target.code length are 0 when `success` is true.
     */
    function verifyCallResultFromTarget(address target, bool success, bytes memory returndata) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @notice Reverts with decoded revert data or FailedInnerCall if no revert
     * data is available.
     *
     * @param returndata Result data of a failed function call.
     *
     * @dev Should throw if returndata length is 0.
     */
    function _revert(bytes memory returndata) private pure {
        if (returndata.length > 0) {
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

/**
 * @title SafeERC20 Library
 *
 * @notice Collection of functions providing utility for safe operations with
 * ERC20 tokens.
 *
 * @dev This is mainly for the usage of token that throw on failure (when the
 * token contract returns false). Tokens that return no value (and instead revert
 * or throw on failure) are also supported where non-reverting calls are assumed
 * to be a successful transaction.
 */
library SafeERC20 {
    
    // LIBRARY

    using Address for address;

    // ERROR

    /**
     * @notice Error indicating a failed operation during an ERC-20 token transfer.
     *
     * @param token Address of the token contract.
     */
    error SafeERC20FailedOperation(address token);

    // FUNCTION

    /**
     * @notice Safely transfers tokens.
     *
     * @param token ERC20 token interface.
     * @param to Address to which the tokens will be transferred.
     * @param value Amount of tokens to be transferred.
     *
     * @dev Transfer `value` amount of `token` from the calling contract to `to` where
     * non-reverting calls are assumed to be successful if `token` returns no value.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @notice Calls a function on a token contract and reverts if the operation fails.
     *
     * @param token ERC20 token interface.
     * @param data Encoded data of the function call.
     *
     * @dev This imitates a Solidity high-level call such as a regular function call to
     * a contract while relaxing the requirement on the return value.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }
}

/********************************************************************************************
  INTERFACE
********************************************************************************************/

/**
 * @title ERC20 Token Standard Interface
 * 
 * @notice Interface of the ERC-20 standard token as defined in the ERC.
 * 
 * @dev See https://eips.ethereum.org/EIPS/eip-20
 */
interface IERC20 {
    
    // EVENT
    
    /**
     * @notice Emitted when `value` tokens are transferred from
     * one account (`from`) to another (`to`).
     * 
     * @param from The address tokens are transferred from.
     * @param to The address tokens are transferred to.
     * @param value The amount of tokens transferred.
     * 
     * @dev The `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @notice Emitted when the allowance of a `spender` for an `owner`
     * is set by a call to {approve}.
     * 
     * @param owner The address allowing `spender` to spend on their behalf.
     * @param spender The address allowed to spend tokens on behalf of `owner`.
     * @param value The allowance amount set for `spender`.
     * 
     * @dev The `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    // FUNCTION

    /**
     * @notice Returns the value of tokens in existence.
     * 
     * @return The value of the total supply of tokens.
     * 
     * @dev This should get the total token supply.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice Returns the value of tokens owned by `account`.
     * 
     * @param account The address to query the balance for.
     * 
     * @return The token balance of `account`.
     * 
     * @dev This should get the token balance of a specific account.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @notice Moves a `value` amount of tokens from the caller's account to `to`.
     * 
     * @param to The address to transfer tokens to.
     * @param value The amount of tokens to be transferred.
     * 
     * @return A boolean indicating whether the transfer was successful or not.
     * 
     * @dev This should transfer tokens to a specified address and emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @notice Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}.
     * 
     * @param owner The address allowing `spender` to spend on their behalf.
     * @param spender The address allowed to spend tokens on behalf of `owner`.
     * 
     * @return The allowance amount for `spender`.
     * 
     * @dev The return value should be zero by default and
     * changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @notice Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     * 
     * @param spender The address allowed to spend tokens on behalf of the sender.
     * @param value The allowance amount for `spender`.
     * 
     * @return A boolean indicating whether the approval was successful or not.
     * 
     * @dev This should approve `spender` to spend a specified amount of tokens
     * on behalf of the sender and emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @notice Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's allowance.
     * 
     * @param from The address to transfer tokens from.
     * @param to The address to transfer tokens to.
     * @param value The amount of tokens to be transferred.
     * 
     * @return A boolean indicating whether the transfer was successful or not.
     * 
     * @dev This should transfer tokens from one address to another after
     * spending caller's allowance and emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

/**
 * @title ERC20 Token Metadata Interface
 * 
 * @notice Interface for the optional metadata functions of the ERC-20 standard as defined in the ERC.
 * 
 * @dev It extends the IERC20 interface. See https://eips.ethereum.org/EIPS/eip-20
 */
interface IERC20Metadata is IERC20 {

    // FUNCTION
    
    /**
     * @notice Returns the name of the token.
     * 
     * @return The name of the token as a string.
     */
    function name() external view returns (string memory);

    /**
     * @notice Returns the symbol of the token.
     * 
     * @return The symbol of the token as a string.
     */
    function symbol() external view returns (string memory);

    /**
     * @notice Returns the number of decimals used to display the token.
     * 
     * @return The number of decimals as a uint8.
     */
    function decimals() external view returns (uint8);
}

/**
 * @title Common Error Interface
 * 
 * @notice Interface of the common errors not specific to ERC-20 functionalities.
 */
interface ICommonErrors {

    // ERROR

    /**
     * @notice Error indicating that cannot use all current addresses to initiate function.
     */
    error CannotUseAllCurrentAddress();

    /**
     * @notice Error indicating that cannot use all current states to initiate function.
     */
    error CannotUseAllCurrentState();

    /**
     * @notice Error indicating that cannot use all current values to initiate function.
     */
    error CannotUseAllCurrentValue();

    /**
     * @notice Error indicating that the `current` address cannot be used in this context.
     * 
     * @param current Address used in the context.
     */
    error CannotUseCurrentAddress(address current);

    /**
     * @notice Error indicating that the `current` state cannot be used in this context.
     * 
     * @param current Boolean state used in the context.
     */
    error CannotUseCurrentState(bool current);

    /**
     * @notice Error indicating that the `current` value cannot be used in this context.
     * 
     * @param current Value used in the context.
     */
    error CannotUseCurrentValue(uint256 current);

    /**
     * @notice Error indicating that the `invalid` address provided is not a valid address for this context.
     * 
     * @param invalid Address used in the context.
     */
    error InvalidAddress(address invalid);

    /**
     * @notice Error indicating that the `invalid` value provided is not a valid value for this context.
     * 
     * @param invalid Value used in the context.
     */
    error InvalidValue(uint256 invalid);
}

/**
 * @title Revenue Share Interface
 * 
 * @notice Interface of the revenue share contract utilised within the ecosystem.
 */
interface IRevenueShare {

    // FUNCTION

    /**
     * @notice Allow deposits to be made along with the creation of new reward pool.
     */
    function deposit() external payable;

    /**
     * @notice Add the share eligible for dividend after token buy transaction.
     * 
     * @param holder The address of the holder.
     * @param amount The amount being transacted.
     */
    function addShare(address holder, uint256 amount) external;

    /**
     * @notice Remove the share eligible for dividend after token transfer or sell transaction.
     * 
     * @param holder The address of the holder.
     * @param amount The amount being transacted.
     */
    function removeShare(address holder, uint256 amount) external;
}

/********************************************************************************************
  ACCESS
********************************************************************************************/

/**
 * @title Ownable Contract
 * 
 * @notice Abstract contract module implementing ownership functionality through
 * inheritance as a basic access control mechanism, where there is an owner account
 * that can be granted exclusive access to specific functions.
 * 
 * @dev The initial owner is set to the address provided by the deployer and can
 * later be changed with {transferOwnership}.
 */
abstract contract Ownable {

    // DATA

    address private _owner;

    // MODIFIER

    /**
     * @notice Modifier that allows access only to the contract owner.
     *
     * @dev Should throw if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    // ERROR

    /**
     * @notice Error indicating that the `account` is not authorized to perform an operation.
     * 
     * @param account Address used to perform the operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @notice Error indicating that the provided `owner` address is invalid.
     * 
     * @param owner Address used to perform the operation.
     * 
     * @dev Should throw if called by an invalid owner account such as address(0) as an example.
     */
    error OwnableInvalidOwner(address owner);

    // CONSTRUCTOR

    /**
     * @notice Initializes the contract setting the `initialOwner` address provided by
     * the deployer as the initial owner.
     * 
     * @param initialOwner The address to set as the initial owner.
     *
     * @dev Should throw an error if called with address(0) as the `initialOwner`.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }
    
    // EVENT
    
    /**
     * @notice Emitted when ownership of the contract is transferred.
     * 
     * @param previousOwner The address of the previous owner.
     * @param newOwner The address of the new owner.
     */
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    // FUNCTION

    /**
     * @notice Get the address of the smart contract owner.
     * 
     * @return The address of the current owner.
     *
     * @dev Should return the address of the current smart contract owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }
    
    /**
     * @notice Checks if the caller is the owner and reverts if not.
     * 
     * @dev Should throw if the sender is not the current owner of the smart contract.
     */
    function _checkOwner() internal view virtual {
        if (owner() != msg.sender) {
            revert OwnableUnauthorizedAccount(msg.sender);
        }
    }
    
    /**
     * @notice Allows the current owner to renounce ownership and make the
     * smart contract ownerless.
     * 
     * @dev This function can only be called by the current owner and will
     * render all `onlyOwner` functions inoperable.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }
    
    /**
     * @notice Allows the current owner to transfer ownership of the smart contract
     * to `newOwner` address.
     * 
     * @param newOwner The address to transfer ownership to.
     *
     * @dev This function can only be called by the current owner and will render
     * all `onlyOwner` functions inoperable to him/her. Should throw if called with
     * address(0) as the `newOwner`.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }
    
    /**
     * @notice Internal function to transfer ownership of the smart contract
     * to `newOwner` address.
     * 
     * @param newOwner The address to transfer ownership to.
     *
     * @dev This function replace current owner address stored as _owner with 
     * the address of the `newOwner`.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

/**
 * @title Auth Contract
 * 
 * @notice Abstract contract module for managing authorization.
 *
 * @dev This contract provides functionality for authorizing and unauthorizing accounts.
 */
abstract contract Auth is Ownable {
    
    // MAPPING

    mapping(address => bool) public authorization;

    // MODIFIER

    modifier authorized() {
        _checkAuthorized();
        _;
    }

    // ERROR

    /**
     * @notice Error indicating that the account is not authorized.
     * 
     * @dev Should throw if called when the account was not authorized.
     */
    error InvalidAuthorizedAccount(address account);

    /**
     * @notice Error indicating that current state is being used.
     * 
     * @dev Should throw if called when the current state is being used.
     */
    error CurrentAuthorizedState(address account, bool state);
    
    // CONSTRUCTOR

    constructor(
        address initialOwner
    ) Ownable(initialOwner) {
        authorize(initialOwner);
        if (initialOwner != msg.sender) {
            authorize(msg.sender);
        }
    }

    // EVENT
    
    /**
     * @notice Emitted when the account's authorization status was updated.
     * 
     * @param state The new state being used for the account.
     * @param authorizedAccount The address of the account being updated.
     * @param caller The address of the caller who update the account.
     * @param timestamp The timestamp when the account was updated.
     */
    event UpdateAuthorizedAccount(address authorizedAccount, address caller, bool state, uint256 timestamp);

    // FUNCTION

    /**
     * @notice Checks if the caller is authorized.
     * 
     * @dev This function checks whether the caller is authorized by verifying their
     * presence in the authorization mapping. If the caller is not authorized, the
     * function reverts with an appropriate error message.
     */
    function _checkAuthorized() internal view virtual {
        if (!authorization[msg.sender]) {
            revert OwnableUnauthorizedAccount(msg.sender);
        }
    }

    /**
     * @notice Authorizes an account.
     * 
     * @param account The address of the account to be authorized.
     * 
     * @dev This function authorizes the specified account by updating the authorization mapping.
     * It checks if the account address is valid and not equal to address(0) or address(0xdead).
     */
    function authorize(address account) public virtual onlyOwner {
        if (account == address(0) || account == address(0xdead)) {
            revert InvalidAuthorizedAccount(account);
        }
        _authorization(account, msg.sender, true);
    }

    /**
     * @notice Unauthorizes an account.
     * 
     * @param account The address of the account to be unauthorized.
     * 
     * @dev This function unauthorizes the specified account by updating the authorization mapping.
     * It checks if the account address is valid and not equal to address(0) or address(0xdead).
     */
    function unauthorize(address account) public virtual onlyOwner {
        if (account == address(0) || account == address(0xdead)) {
            revert InvalidAuthorizedAccount(account);
        }
        _authorization(account, msg.sender, false);
    }

    /**
     * @notice Internal function for managing authorization status.
     * 
     * @param account The address of the account to be authorized or unauthorized.
     * @param caller The address of the caller authorizing or unauthorizing the account.
     * @param state The desired authorization state (true for authorized, false for unauthorized).
     * 
     * @dev This function updates the authorization mapping for the specified account and emits an
     * `UpdateAuthorizedAccount` event. It checks if the current authorization state matches the
     * desired state before updating.
     */
    function _authorization(address account, address caller, bool state) internal virtual {
        if (authorization[account] == state) {
            revert CurrentAuthorizedState(account, state);
        }
        authorization[account] = state;
        emit UpdateAuthorizedAccount(account, caller, state, block.timestamp);
    }
}

/********************************************************************************************
  SECURITY
********************************************************************************************/

/**
 * @title Pausable Contract
 * 
 * @notice Abstract contract module implementing pause functionality through
 * inheritance as a basic security mechanism, where there certain functions
 * that can be paused and unpaused.
 */
abstract contract Pausable {

    // DATA

    bool private _paused;

    // ERROR

    /**
     * @notice Error thrown when an action is attempted in an enforced pause.
     */
    error EnforcedPause();

    /**
     * @notice Error thrown when an action is attempted without the expected pause.
     */
    error ExpectedPause();

    // MODIFIER

    /**
     * @notice Modifier ensure functions are called when the contract is
     * not paused.
     * 
     * @dev Should throw if called when the contract is paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @notice Modifier ensure functions are called when the contract is
     * paused.
     * 
     * @dev Should throw if called when the contract is not paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    // CONSTRUCTOR

    /**
     * @notice Initializes the contract setting the `_paused` state as false.
     */
    constructor() {
        _paused = false;
    }

    // EVENT
    
    /**
     * @notice Emitted when the contract is paused.
     * 
     * @param account The address that initiate the function.
     */
    event Paused(address account);

    /**
     * @notice Emitted when the contract is unpaused.
     * 
     * @param account The address that initiate the function.
     */
    event Unpaused(address account);

    // FUNCTION

    /**
     * @notice Returns the current paused state of the contract.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @notice Function to pause the contract.
     * 
     * @dev This function is accessible externally when not paused.
     */
    function pause() public virtual whenNotPaused {
        _pause();
    }

    /**
     * @notice Function to unpause the contract.
     * 
     * @dev This function is accessible externally when paused.
     */
    function unpause() public virtual whenPaused {
        _unpause();
    }

    /**
     * @notice Internal function to revert if the contract is not paused.
     * 
     * @dev Throws when smart contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @notice Internal function to revert if the contract is paused.
     * 
     * @dev Throws when smart contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @notice Internal function to pause the contract.
     * 
     * @dev This function emits {Paused} event.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(msg.sender);
    }

    /**
     * @notice Internal function to unpause the contract.
     * 
     * @dev This function emits {Unpaused} event.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(msg.sender);
    }
}

/********************************************************************************************
  DISTRIBUTOR
********************************************************************************************/

/**
 * @title BRO Distributor Contract
 *
 * @notice BRO Distributor is the smart contract used for the revenue share system within
 * the BRO ecosystem.
 * 
 * @dev Implements RevenueShare and CommonError interfaces, and extends Auth contract while
 * at the same time implementing the Pausable logic.
 */
contract Distributor is Auth, Pausable, ICommonErrors, IRevenueShare {

    // LIBRARY

    using SafeERC20 for IERC20;
    using Address for address;

    // DATA

    struct PenaltyHistory {
        address holder;
        uint256 penaltyTime;
        uint256 totalReward;
        uint256 startIndex;
        uint256 lastIndex;
    }

    struct RewardHistory {
        address holder;
        uint256 distributedTime;
        uint256 totalReward;
        uint256 startIndex;
        uint256 lastIndex;
    }

    struct RewardInfo {
        uint256 createTime;
        uint256 amountAdded;
        uint256 shareEligible;
        uint256 rewardsPerShare;
        uint256 rewardsPerShareAccuracyFactor;
    }

    struct ShareInfo {
        uint256 lastTxn;
        uint256 cooldown;
        uint256 shares;
        uint256 startPoolIndex;
        uint256 eligibleTime;
    }

    address public projectOwner;
    address public tokenAddress;

    uint256 public lastTally = 0;
    uint256 public rewardHistoryIndex = 0;
    uint256 public penaltyHistoryIndex = 0;
    uint256 public totalHolders = 0;
    uint256 public totalShares = 0;
    uint256 public totalRewardPool = 0;
    uint256 public allocatedFund = 0;
    uint256 public distributedFund = 0;
    uint256 public accuracyFactor = 0;
    uint256 public lastRewardAddedTimestamp = 0;
    uint256 public maximumAmountPerEpoch = 18_000 ether;
    uint256 public minimumRewardRequired = 0.1 ether;
    uint256 public minimumForRewardPool = 10 ether;
    uint256 public minimumBalanceEligible = 3_000 ether;
    uint256 public cooldownTime = 7 hours;

    bool public justCreatePool = false;
    bool public useAddShareCooldown = false;
    bool public useRemoveShareCooldown = true;

    // MAPPING
    
    mapping(address account => ShareInfo) public userEligibility;
    mapping(address account => uint256) public remainingTransactable;
    mapping(address account => uint256) public holderIndex;
    mapping(uint256 holderId => address) public holderAtIndex;
    mapping(uint256 poolId => RewardInfo) public rewardPool;
    mapping(uint256 historyId => RewardHistory) public rewardHistory;
    mapping(address account => uint256) public userRewardHistoryIndex;
    mapping(address account => mapping(uint256 index => uint256)) public userRewardHistory;
    mapping(uint256 penaltyId => PenaltyHistory) public penaltyHistory;
    mapping(address account => uint256) public userPenaltyHistoryIndex;
    mapping(address account => mapping(uint256 index => uint256)) public userPenaltyHistory;

    // ERROR

    /**
     * @notice Error indicating that the native cannot be withdrawn from the smart contract.
     */
    error CannotWithdrawNative();

    /**
     * @notice Error indicating that the native fund in the smart contract is insufficient.
     */
    error InsufficientFund();

    /**
     * @notice Error indicating that the receiver cannot initiate transfer of Ether.
     * 
     * @dev Should throw if called by the receiver address.
     */
    error ReceiverCannotInitiateTransferEther();

    /**
     * @notice Error indicating cooldown has not end.
     */
    error WaitForCooldown(uint256 currentTime, uint256 endTime, uint256 timeLeft);

    /**
     * @notice Error indicating that the function not initiated by token contract.
     * 
     * @dev Should throw if called by address other than token.
     */
    error NotInitiatedByToken(address caller);

    /**
     * @notice Error indicating that the `sender` has insufficient `balance` for the operation.
     * 
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     *
     * @dev The `needed` value is required to inform user on the needed amount.
     */
    error InsufficientBalance(address sender, uint256 balance, uint256 needed);

    // MODIFIER
    
    /**
     * @notice Modifier to only allow token to initiate the function.
     */
    modifier onlyToken() {
        if (tokenAddress != msg.sender) {
            revert NotInitiatedByToken(msg.sender);
        }
        _;
    }

    // CONSTRUCTOR

    /**
     * @notice Constructs the distributor contract and initializes all important settings.
     */
    constructor(
        address initialOwner,
        address token
    ) Auth (
        msg.sender
    ) payable {
        if (initialOwner == address(0) || initialOwner == address(0xdead)) {
            revert InvalidAddress(initialOwner);
        }
        if (initialOwner != msg.sender) {
            authorize(initialOwner);
        }
        tokenAddress = token;
        projectOwner = initialOwner;
        accuracyFactor = (1 ether) * (1 ether);
    }

    // EVENT

    /**
     * @notice Emitted when the value of a feature is updated.
     * 
     * @param valueType The type of value being updated.
     * @param oldValue The previous status before the update.
     * @param newValue The new status after the update.
     * @param caller The address of the caller who updated the value.
     * @param timestamp The timestamp when the update occurred.
     */
    event UpdateValue(string valueType, uint256 oldValue, uint256 newValue, address caller, uint256 timestamp);

    /**
     * @notice Emitted when the state of a feature is updated.
     * 
     * @param stateType The type of state being updated.
     * @param oldState The previous status before the update.
     * @param newState The new status after the update.
     * @param caller The address of the caller who updated the state.
     * @param timestamp The timestamp when the update occurred.
     */
    event UpdateState(string stateType, bool oldState, bool newState, address caller, uint256 timestamp);

    /**
     * @notice Emitted when the claiming reward.
     * 
     * @param holder The address of the holder who claimed the reward.
     * @param rewardClaimed The total amount of reward claimed.
     * @param startIndex The reward pool index where claim start occuring.
     * @param lastIndex The reward pool index where claim stop occuring.
     * @param timestamp The timestamp when the claim occurred.
     */
    event RewardClaimed(address holder, uint256 rewardClaimed, uint256 startIndex, uint256 lastIndex, uint256 timestamp);

    /**
     * @notice Emitted when taking penalty.
     * 
     * @param holder The address of the holder who received the penalty.
     * @param penaltyTaken The total amount of penalty taken.
     * @param startIndex The reward pool index where penalty start occuring.
     * @param lastIndex The reward pool index where penalty stop occuring.
     * @param timestamp The timestamp when the penalty occurred.
     */
    event PenaltyTaken(address holder, uint256 penaltyTaken, uint256 startIndex, uint256 lastIndex, uint256 timestamp);

    /**
     * @notice Emitted when deposit for reward pool was created on the contract.
     * 
     * @param poolId The id of the reward pool created.
     * @param rewardAdded The amount of reward added for the pool.
     * @param caller The address that triggered the reward pool creation.
     * @param timestamp The timestamp when reward pool was created.
     */
    event RewardDeposited(uint256 poolId, uint256 rewardAdded, address caller, uint256 timestamp);

    /**
     * @notice Emitted when share was added after a user buy token.
     * 
     * @param holder The address of the user whose share is being updated.
     * @param amount The amount of share being added for the user.
     * @param caller The address that triggered the share update.
     * @param timestamp The timestamp when share was updated.
     */
    event AddShare(address holder, uint256 amount, address caller, uint256 timestamp);

    /**
     * @notice Emitted when share was removed after a user sell or transfer token.
     * 
     * @param holder The address of the user whose share is being updated.
     * @param amount The amount of share being removed for the user.
     * @param caller The address that triggered the share update.
     * @param timestamp The timestamp when share was updated.
     */
    event RemoveShare(address holder, uint256 amount, address caller, uint256 timestamp);

    /**
     * @notice Emitted when reward distribution was updated to balance out the difference.
     * 
     * @param desiredAmount The amount to be expected.
     * @param oldAmount The amount currently in use.
     * @param newAmount The amount after the update.
     * @param caller The address that triggered the update.
     * @param timestamp The timestamp for the update.
     */
    event TallyDistribution(uint256 desiredAmount, uint256 oldAmount, uint256 newAmount, address caller, uint256 timestamp);

    // FUNCTION

    /* General */
    
    /**
     * @notice Allows the contract to receive Ether.
     * 
     * @dev This is a required feature to have in order to allow the smart contract
     * to be able to receive ether from the swap.
     */
    receive() external payable {}

    /**
     * @notice Withdraws tokens or Ether from the contract to a specified address.
     * 
     * @param token The address of the token to withdraw.
     * @param amount The amount of tokens or Ether to withdraw.
     * 
     * @dev You need to use address(0) as `tokenAddress` to withdraw Ether and
     * use 0 as `amount` to withdraw the whole balance amount in the smart contract.
     * Anyone can trigger this function to send the fund to the `feeReceiver`.
     * Only `feeReceiver` address will not be able to trigger this function to
     * withdraw Ether from the smart contract by himself/herself. Should throw if try
     * to withdraw any amount of native token from the smart contract. Distribution
     * of native token can only be done through autoRedeem function.
     */
    function wTokens(address token, uint256 amount) external {
        uint256 locked = allocatedFund > distributedFund ? allocatedFund - distributedFund : 0;
        uint256 toTransfer = amount;
        address receiver = projectOwner;
        
        if (token == address(0)) {
            if (locked >= address(this).balance) {
                revert CannotWithdrawNative();
            }
            if (amount > address(this).balance - locked) {
                revert InsufficientFund();
            }
            if (msg.sender == receiver) {
                revert ReceiverCannotInitiateTransferEther();
            }
            if (amount == 0) {
                toTransfer = address(this).balance - locked;
            }
            payable(receiver).transfer(toTransfer);
        } else {
            if (amount == 0) {
                toTransfer = IERC20(token).balanceOf(address(this));
            }
            IERC20(token).safeTransfer(receiver, toTransfer);
        }
    }

    /**
     * @notice A function to adjust and tally the difference between amount of reward
     * distributed to amount of reward allocated as the result from the nature of how
     * calculation was carried out in Solidity.
     * 
     * @dev Should throw if using an invalid value.
     */
    function tallyDistribution(uint256 amount) external onlyOwner {
        if (amount < 1 && amount > 5) {
            revert InvalidValue(amount);
        }
        if (lastTally + 1 hours > block.timestamp) {
            revert WaitForCooldown(block.timestamp, lastTally + 1 hours, (lastTally + 1 hours) - block.timestamp);
        }
        uint256 oldAmount = distributedFund;
        distributedFund += amount;
        lastTally = block.timestamp;
        emit TallyDistribution(allocatedFund, oldAmount, distributedFund + amount, msg.sender, block.timestamp);
    }

    /* Check */

    /**
     * @notice Checks if using current state.
     * 
     * @dev Should throw if using current state.
     */
    function checkCurrentState(bool newState, bool current) internal pure {
        if (newState == current) {
            revert CannotUseCurrentState(newState);
        }
    }

    /**
     * @notice Checks if using current value.
     * 
     * @dev Should throw if using current value.
     */
    function checkCurrentValue(uint256 newValue, uint256 current) internal pure {
        if (newValue == current) {
            revert CannotUseCurrentValue(newValue);
        }
    }
    
    /**
     * @notice Checks if using invalid value.
     * 
     * @dev Should throw if using invalid value.
     */
    function checkInvalidValue(uint256 newValue, uint256 invalid) internal pure {
        if (newValue == invalid) {
            revert InvalidValue(newValue);
        }
    }

    /**
     * @notice Checks if using current address.
     * 
     * @dev Should throw if using current address.
     */
    function checkCurrentAddress(address newAddress, address current) internal pure {
        if (newAddress == current) {
            revert CannotUseCurrentAddress(newAddress);
        }
    }
    
    /**
     * @notice Checks if using invalid address.
     * 
     * @dev Should throw if using invalid address.
     */
    function checkInvalidAddress(address newAddress, address invalid) internal pure {
        if (newAddress == invalid) {
            revert InvalidAddress(newAddress);
        }
    }

    /**
     * @notice Checks the transaction involve cooldown and penalty.
     * 
     * @param holder The address that will be doing the transaction.
     * @param amount The amount that will be used for the transaction.
     * 
     * @return The state to show if the transaction will exceed amount allowed per reward epoch.
     * @return The duration left for cooldown.
     * @return The amount left that was allowed to be transacted per reward epoch.
     */
    function checkCooldownPenalty(address holder, uint256 amount) public view returns (bool, uint256, uint256) {
        uint256 lastTxn = userEligibility[holder].lastTxn;
        uint256 cooldown = userEligibility[holder].cooldown;
        bool exceed = false;

        if (
            (
                cooldown < 1 ||
                block.timestamp - lastTxn > cooldownTime ||
                block.timestamp - lastTxn >= cooldown
            ) && (
                amount <= maximumAmountPerEpoch &&
                amount <= remainingTransactable[holder]
            )
        ) {
            return (
                exceed, 
                block.timestamp - lastTxn > cooldownTime ||
                block.timestamp - lastTxn >= cooldown ? 
                    cooldownTime
                :
                    cooldown - (block.timestamp - lastTxn), 
                remainingTransactable[holder] > amount ? 
                    remainingTransactable[holder] - amount 
                :
                    0
            );
        } else {
            exceed = remainingTransactable[holder] < amount;
            return (
                exceed, 
                cooldown - (block.timestamp - lastTxn), 
            remainingTransactable[holder] > amount ? 
                remainingTransactable[holder] - amount 
            :
                0
            );
        }
    }

    /* Update */

    /**
     * @notice Updates the value of maximumAmountPerEpoch.
     * 
     * @param newValue The new value of maximumAmountPerEpoch to be set.
     * 
     * @dev This function will emits the UpdateValue event.
     */
    function updateMaximumAmountPerEpoch(uint256 newValue) external authorized {
        if (newValue < 50 ether) {
            revert InvalidValue(newValue);
        }
        checkCurrentValue(newValue, maximumAmountPerEpoch);
        uint256 oldValue = maximumAmountPerEpoch;
        maximumAmountPerEpoch = newValue;
        emit UpdateValue("maximumAmountPerEpoch", oldValue, newValue, msg.sender, block.timestamp);
    }

    /**
     * @notice Updates the value of minimumRewardRequired.
     * 
     * @param newValue The new value of minimumRewardRequired to be set.
     * 
     * @dev This function will emits the UpdateValue event.
     */
    function updateMinimumRewardRequired(uint256 newValue) external authorized {
        if (newValue > 1 ether) {
            revert InvalidValue(newValue);
        }
        checkCurrentValue(newValue, minimumRewardRequired);
        uint256 oldValue = minimumRewardRequired;
        minimumRewardRequired = newValue;
        emit UpdateValue("minimumRewardRequired", oldValue, newValue, msg.sender, block.timestamp);
    }

    /**
     * @notice Updates the value of minimumForRewardPool.
     * 
     * @param newValue The new value of minimumForRewardPool to be set.
     * 
     * @dev This function will emits the UpdateValue event.
     */
    function updateMinimumForRewardPool(uint256 newValue) external authorized {
        if (newValue < 1 ether) {
            revert InvalidValue(newValue);
        }
        checkCurrentValue(newValue, minimumForRewardPool);
        uint256 oldValue = minimumForRewardPool;
        minimumForRewardPool = newValue;
        emit UpdateValue("minimumForRewardPool", oldValue, newValue, msg.sender, block.timestamp);
    }

    /**
     * @notice Updates the value of minimumBalanceEligible.
     * 
     * @param newValue The new value of minimumBalanceEligible to be set.
     * 
     * @dev This function will emits the UpdateValue event.
     */
    function updateMinimumBalanceEligible(uint256 newValue) external authorized {
        if (newValue < 0) {
            revert InvalidValue(newValue);
        }
        checkCurrentValue(newValue, minimumBalanceEligible);
        uint256 oldValue = minimumBalanceEligible;
        minimumBalanceEligible = newValue;
        emit UpdateValue("minimumBalanceEligible", oldValue, newValue, msg.sender, block.timestamp);
    }

    /**
     * @notice Updates the value of cooldownTime.
     * 
     * @param newValue The new value of cooldownTime to be set.
     * 
     * @dev This function will emits the UpdateValue event.
     */
    function updateCooldownTime(uint256 newValue) external authorized {
        if (newValue < 30 minutes || newValue > 7 days) {
            revert InvalidValue(newValue);
        }
        checkCurrentValue(newValue, cooldownTime);
        uint256 oldValue = cooldownTime;
        cooldownTime = newValue;
        emit UpdateValue("cooldownTime", oldValue, newValue, msg.sender, block.timestamp);
    }

    /**
     * @notice Updates the state of useAddShareCooldown.
     * 
     * @param newState The new state of useAddShareCooldown to be set.
     * 
     * @dev This function will emits the UpdateState event.
     */
    function updateUseAddShareCooldown(bool newState) external authorized {
        checkCurrentState(newState, useAddShareCooldown);
        bool oldState = useAddShareCooldown;
        useAddShareCooldown = newState;
        emit UpdateState("useAddShareCooldown", oldState, newState, msg.sender, block.timestamp);
    }

    /**
     * @notice Updates the state of useRemoveShareCooldown.
     * 
     * @param newState The new state of useRemoveShareCooldown to be set.
     * 
     * @dev This function will emits the UpdateState event.
     */
    function updateUseRemoveShareCooldown(bool newState) external authorized {
        checkCurrentState(newState, useRemoveShareCooldown);
        bool oldState = useRemoveShareCooldown;
        useRemoveShareCooldown = newState;
        emit UpdateState("useRemoveShareCooldown", oldState, newState, msg.sender, block.timestamp);
    }

    /* Reward */

    /**
     * @notice Allow deposits to be made along with the creation of new reward pool.
     * 
     * @dev This function will allow new reward pool to be created if the conditions were met.
     */
    function deposit() external payable override whenNotPaused {
        if (allocatedFund < distributedFund) {
            uint256 diff = distributedFund - allocatedFund;
            distributedFund -= diff;
        }

        uint256 rewardBalance = allocatedFund > distributedFund ? allocatedFund - distributedFund : 0;
        uint256 current = address(this).balance - rewardBalance;

        if (
            totalHolders > 0 &&
            totalShares > 0 &&
            current >= minimumForRewardPool &&
            lastRewardAddedTimestamp + cooldownTime <= block.timestamp
        ) {
            justCreatePool = true;
            totalRewardPool = totalRewardPool + 1;
            allocatedFund = allocatedFund + current;
            lastRewardAddedTimestamp = block.timestamp;
            rewardPool[totalRewardPool].createTime = block.timestamp;
            rewardPool[totalRewardPool].amountAdded = current;
            rewardPool[totalRewardPool].shareEligible = totalShares;
            rewardPool[totalRewardPool].rewardsPerShare = current * accuracyFactor / totalShares;
            rewardPool[totalRewardPool].rewardsPerShareAccuracyFactor = accuracyFactor;
            emit RewardDeposited(totalRewardPool, current, msg.sender, block.timestamp);
        }
    }

    /**
     * @notice Checks all pending rewards for a specific users.
     * 
     * @param holder The address of the user being check.
     * 
     * @dev This function will loop the check from the user's current start pool index.
     */
    function checkAllPendingRewards(address holder) public view returns (uint256) {
        if (holderIndex[holder] < 1 || userEligibility[holder].startPoolIndex > totalRewardPool) {
            return 0;
        }
        return checkPendingRewards(holder, totalRewardPool);
    }

    /**
     * @notice Checks all pending rewards for a specific users from account start index to specific end index.
     * 
     * @param holder The address of the user being check.
     * @param endIndex The last index to check.
     * 
     * @dev This function will loop the check from the given reward pool index range.
     */
    function checkPendingRewards(address holder, uint256 endIndex) public view returns (uint256) {
        uint256 pending = 0;
        uint256 startIndex = userEligibility[holder].startPoolIndex;
        if (endIndex > totalRewardPool) {
            endIndex = totalRewardPool;
        }
        for (uint256 i = startIndex; i < endIndex + 1; i++) {
            uint256 reward = (userEligibility[holder].shares * rewardPool[i].rewardsPerShare) / rewardPool[i].rewardsPerShareAccuracyFactor;
            pending += reward;
        }
        return pending;
    }

    /**
     * @notice Allow users to manually initiate the reward claim functionality.
     * 
     * @dev This function will check the pending rewards from eligible pool and distribute the amount.
     */
    function manualClaimRewards() external {
        claimRewards(msg.sender);
    }

    /**
     * @notice Initiate the reward claim functionality internally.
     * 
     * @dev This function will check the pending rewards from eligible pool and distribute the amount.
     */
    function claimRewards(address holder) public {
        uint256 startIndex = userEligibility[holder].startPoolIndex;
        uint256 lastIndex = startIndex;
        if (lastIndex > totalRewardPool) {
            return;
        }
        if (startIndex < 1) {
            return;
        }
        while (
            rewardPool[lastIndex].createTime + cooldownTime < block.timestamp &&
            lastIndex < totalRewardPool + 1
        ) {
            lastIndex++;
        }

        uint256 pending = checkPendingRewards(holder, lastIndex - 1);
        if (minimumRewardRequired <= pending) {
            userEligibility[holder].startPoolIndex = lastIndex;
            distributedFund += pending;
            rewardHistoryIndex++;
            rewardHistory[rewardHistoryIndex].holder = holder;
            rewardHistory[rewardHistoryIndex].distributedTime = block.timestamp;
            rewardHistory[rewardHistoryIndex].totalReward = pending;
            rewardHistory[rewardHistoryIndex].startIndex = startIndex;
            rewardHistory[rewardHistoryIndex].lastIndex = lastIndex - 1;
            userRewardHistoryIndex[holder] += 1;
            userRewardHistory[holder][userRewardHistoryIndex[holder]] = rewardHistoryIndex;
            payable(holder).transfer(pending);
            emit RewardClaimed(holder, pending, startIndex, lastIndex, block.timestamp);
        }
    }
    
    /* Distributor */

    /**
     * @notice Add the share eligible for dividend after token buy transaction.
     * 
     * @param holder The address of the holder.
     * @param amount The amount being transacted.
     */
    function addShare(address holder, uint256 amount) external override onlyToken whenNotPaused {
        uint256 oldShare = userEligibility[holder].shares;
        uint256 newShare = IERC20(tokenAddress).balanceOf(holder) + amount;
        bool alreadyEligible = oldShare >= minimumBalanceEligible;
        bool isEligible = IERC20(tokenAddress).balanceOf(holder) + amount >= minimumBalanceEligible;
        if (isEligible) {
            if (oldShare < 1) {
                addHolder(holder);
            }
            if (oldShare > 0) {
                if (totalRewardPool > 0 && userEligibility[holder].startPoolIndex > 0 && userEligibility[holder].startPoolIndex < totalRewardPool + 1) {
                    claimRewards(holder);
                }
            }
            userEligibility[holder].shares = newShare;
            totalShares = totalShares - oldShare + newShare;
            emit AddShare(holder, amount, msg.sender, block.timestamp);
        }
        if (alreadyEligible && useAddShareCooldown) {
            checkPenalty(holder, amount, false, false);
        }
    }

    /**
     * @notice Remove the share eligible for dividend after token transfer or sell transaction.
     * 
     * @param holder The address of the holder.
     * @param amount The amount being transacted.
     */
    function removeShare(address holder, uint256 amount) external override onlyToken whenNotPaused {
        uint256 oldShare = userEligibility[holder].shares;
        uint256 newShare = IERC20(tokenAddress).balanceOf(holder) - amount;
        bool alreadyEligible = oldShare >= minimumBalanceEligible;
        bool isEligible = IERC20(tokenAddress).balanceOf(holder) - amount >= minimumBalanceEligible;
        bool poolUpdated = false;
        if (justCreatePool) {
            if (rewardPool[totalRewardPool].createTime + cooldownTime >= block.timestamp) {
                uint256 newTotalShare = rewardPool[totalRewardPool].shareEligible - amount;
                rewardPool[totalRewardPool].shareEligible = newTotalShare;
                rewardPool[totalRewardPool].rewardsPerShare = rewardPool[totalRewardPool].amountAdded * accuracyFactor / newTotalShare;
                poolUpdated = true;
            }
            justCreatePool = false;
        }
        if (alreadyEligible && totalRewardPool > 0 && userEligibility[holder].startPoolIndex > 0 && userEligibility[holder].startPoolIndex < totalRewardPool + 1) {
            claimRewards(holder);
        }
        if (isEligible) {
            userEligibility[holder].shares = newShare;
            totalShares = totalShares - oldShare + newShare;
            emit RemoveShare(holder, amount, msg.sender, block.timestamp);
        }
        if (!isEligible) {
            totalShares = totalShares - oldShare;
            userEligibility[holder].shares = 0;
            removeHolder(holder);
            emit RemoveShare(holder, oldShare, msg.sender, block.timestamp);
        }
        if (alreadyEligible && useRemoveShareCooldown) {
            checkPenalty(holder, amount, true, poolUpdated);
        }
    }

    /**
     * @notice Check and take action if penalty incurred on the transaction.
     * 
     * @param holder The address of the holder.
     * @param amount The amount being transacted.
     * @param removal The status of amount transacted whether it is removed or added.
     * @param poolUpdated The status on whether latest reward pool has been updated.
     */
    function checkPenalty(address holder, uint256 amount, bool removal, bool poolUpdated) internal {
        uint256 lastTxn = userEligibility[holder].lastTxn;
        uint256 cooldown = userEligibility[holder].cooldown;
        bool exceed = false;
        
        if (
            cooldown < 1 ||
            block.timestamp - lastTxn > cooldownTime ||
            block.timestamp - lastTxn >= userEligibility[holder].cooldown
        ) {
            userEligibility[holder].cooldown = cooldownTime;
            remainingTransactable[holder] = maximumAmountPerEpoch;
        } else {
            exceed = remainingTransactable[holder] < amount;
            userEligibility[holder].cooldown -= (block.timestamp - lastTxn);
            remainingTransactable[holder] = remainingTransactable[holder] > amount ? remainingTransactable[holder] - amount : 0;
        }

        userEligibility[holder].lastTxn = block.timestamp;

        uint256 startIndex = userEligibility[holder].startPoolIndex;
        uint256 lastIndex = startIndex;

        while (
            rewardPool[lastIndex].createTime + cooldownTime > block.timestamp &&
            lastIndex < totalRewardPool + 1
        ) {
            lastIndex++;
        }

        if (removal && startIndex < lastIndex) {
            uint256 count = startIndex;
            while (count < lastIndex) {
                if (!poolUpdated) {
                    uint256 newTotalShare = rewardPool[count].shareEligible - amount;
                    rewardPool[count].shareEligible = newTotalShare;
                    rewardPool[count].rewardsPerShare = rewardPool[count].amountAdded * accuracyFactor / newTotalShare;
                } else {
                    if (count < lastIndex - 1) {
                        uint256 newTotalShare = rewardPool[count].shareEligible - amount;
                        rewardPool[count].shareEligible = newTotalShare;
                        rewardPool[count].rewardsPerShare = rewardPool[count].amountAdded * accuracyFactor / newTotalShare;
                    }
                }
                count++;
            }
        }

        if (exceed) {
            uint256 pending = checkPendingRewards(holder, lastIndex - 1);
            userEligibility[holder].startPoolIndex = lastIndex;
            distributedFund += pending;

            penaltyHistoryIndex++;
            penaltyHistory[penaltyHistoryIndex].holder = holder;
            penaltyHistory[penaltyHistoryIndex].penaltyTime = block.timestamp;
            penaltyHistory[penaltyHistoryIndex].totalReward = pending;
            penaltyHistory[penaltyHistoryIndex].startIndex = startIndex;
            penaltyHistory[penaltyHistoryIndex].lastIndex = lastIndex - 1;
            userPenaltyHistoryIndex[holder] += 1;
            userPenaltyHistory[holder][userPenaltyHistoryIndex[holder]] = penaltyHistoryIndex;
            emit PenaltyTaken(holder, pending, startIndex, lastIndex, block.timestamp);
        }
    }

    /* Holders */

    /*
     * @notice Adds a holder to the list of eligible holder.
     * 
     * @param holder The address of the holder being added.
     * 
     * @dev This function is internal and should only be called within the contract.
     */
    function addHolder(address holder) internal {
        totalHolders++;
        holderAtIndex[totalHolders] = holder;
        holderIndex[holder] = totalHolders;
        userEligibility[holder].eligibleTime = block.timestamp;
        userEligibility[holder].startPoolIndex = totalRewardPool + 1;
    }

    /**
     * @notice Removes a holder from the list of eligible holder.
     * 
     * @param holder The address of the holder being removed.
     * 
     * @dev This function is internal and should only be called within the contract.
     */
    function removeHolder(address holder) internal {
        uint256 currentIndex = holderIndex[holder];
        address lastHolder = holderAtIndex[totalHolders];
        
        holderIndex[lastHolder] = currentIndex;
        holderAtIndex[currentIndex] = lastHolder;
        holderIndex[holder] = 0;
        holderAtIndex[totalHolders] = address(0);
        userEligibility[holder].eligibleTime = 0;
        userEligibility[holder].startPoolIndex = 0;

        totalHolders--;
    }
    
    /* Override */
    
    /**
     * @notice Overrides the {transferOwnership} function to update project owner.
     * 
     * @param newOwner The address of the new owner.
     * 
     * @dev Should throw if the `newOwner` is set to the current owner address or address(0xdead).
     * This overrides function is just an extended version of the original {transferOwnership}
     * function. See {Ownable-transferOwnership} for more information.
     */
    function transferOwnership(address newOwner) public override onlyOwner {
        checkCurrentAddress(newOwner, owner());
        checkInvalidAddress(newOwner, address(0xdead));
        projectOwner = newOwner;
        super.transferOwnership(newOwner);
    }
    
    /**
     * @notice Overrides the {pause} function to pause the contract.
     * 
     * @dev This function is accessible externally when not paused only by authorized account.
     */
    function pause() public override whenNotPaused authorized {
        super.pause();
    }

    /**
     * @notice Overrides the {unpause} function to pause the contract.
     * 
     * @dev This function is accessible externally when paused only by authorized account.
     */
    function unpause() public override whenPaused authorized {
        super.unpause();
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):