Contract Diff Checker

Contract Name:
CypherDogToken

Contract Source Code:

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

// SPDX-License-Identifier: MIT
// based on https://github.com/CoinbaseStablecoin/eip-3009

pragma solidity ^0.8.0;

abstract contract EIP712Domain {
    /**
     * @dev EIP712 Domain Separator
     */
    bytes32 public DOMAIN_SEPARATOR;
    uint256 public CHAINID;
    bytes32 public EIP712_DOMAIN_TYPEHASH;
}

error InvalidSignature();
error InvalidS();
error InvalidV();

/**
 * @title ECRecover
 * @notice A library that provides a safe ECDSA recovery function
 */
library ECRecover {
    /**
     * @notice Recover signer's address from a signed message
     * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
     * Modifications: Accept v, r, and s as separate arguments
     * @param digest    Keccak-256 hash digest of the signed message
     * @param v         v of the signature
     * @param r         r of the signature
     * @param s         s of the signature
     * @return Signer address
     */
    function recover(
        bytes32 digest,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (
            uint256(s) >
            0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
        ) {
            revert InvalidS();
        }

        if (v < 27 || v > 28) {
            revert InvalidV();
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(digest, v, r, s);
        if (signer == address(0)) revert InvalidSignature();

        return signer;
    }
}

/**
 * @title EIP712
 * @notice A library that provides EIP712 helper functions
 */
library EIP712 {
    // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
    bytes32 public constant EIP712_DOMAIN_TYPEHASH =
        0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;

    /**
     * @notice Make EIP712 domain separator
     * @param name      Contract name
     * @param version   Contract version
     * @return Domain separator
     */
    function makeDomainSeparator(
        string memory name,
        string memory version,
        uint256 chainId
    ) internal view returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    EIP712_DOMAIN_TYPEHASH,
                    keccak256(bytes(name)),
                    keccak256(bytes(version)),
                    chainId,
                    address(this)
                )
            );
    }

    /**
     * @notice Recover signer's address from a EIP712 signature
     * @param domainSeparator   Domain separator
     * @param v                 v of the signature
     * @param r                 r of the signature
     * @param s                 s of the signature
     * @param typeHashAndData   Type hash concatenated with data
     * @return Signer's address
     */
    function recover(
        bytes32 domainSeparator,
        uint8 v,
        bytes32 r,
        bytes32 s,
        bytes memory typeHashAndData
    ) internal pure returns (address) {
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                domainSeparator,
                keccak256(typeHashAndData)
            )
        );
        return ECRecover.recover(digest, v, r, s);
    }
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT

pragma solidity ^0.8.0;

/**
    Internal functions from ERC20 token to be used for IEP2612 and IEP3009
 */
abstract contract ERC20Internal {
    // internal _approve call
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual;

    // internal _transfer call
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual;
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT

pragma solidity ^0.8.0;

/**
    Ownership contract
    Modified https://eips.ethereum.org/EIPS/eip-173
    A confirmation of ownership transfer has been added
     to prevent ownership from being transferred to the wrong address
 */
contract Ownable {
    /// Current contract owner
    address public owner;
    /// New contract owner to be confirmed
    address public newOwner;
    /// Emit on every owner change
    event OwnershipChanged(address indexed from, address indexed to);

    /**
        Set default owner as contract deployer
     */
    constructor() {
        owner = msg.sender;
    }

    /**
        Use this modifier to limit function to contract owner
     */
    modifier onlyOwner() {
        require(msg.sender == owner, "Only for Owner");
        _;
    }

    /**
        Prepare to change ownersip. New owner need to confirm it.
        @param user address delegated to be new contract owner
     */
    function giveOwnership(address user) external onlyOwner {
        require(user != address(0x0), "renounceOwnership() instead");
        newOwner = user;
    }

    /**
        Accept contract ownership by new owner.
     */
    function acceptOwnership() external {
        require(
            newOwner != address(0x0) && msg.sender == newOwner,
            "Only newOwner can accept"
        );
        emit OwnershipChanged(owner, newOwner);
        owner = newOwner;
        newOwner = address(0x0);
    }

    /**
        Renounce ownership of the contract.
        Any function uses "onlyOwner" modifier will be inaccessible.
     */
    function renounceOwnership() external onlyOwner {
        emit OwnershipChanged(owner, address(0x0));
        owner = address(0x0);
    }
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT

pragma solidity ^0.8.0;

/**
    Full ERC20 interface
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * ////////IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT
// Following https://eips.ethereum.org/EIPS/eip-3009
// based on https://github.com/CoinbaseStablecoin/eip-3009

pragma solidity ^0.8.0;

////import "./ERC20Internal.sol";
////import "./EIP712.sol";

/**
    EIP3009 implementation
 */
abstract contract ERC3009 is ERC20Internal, EIP712Domain {
    // events
    event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
    event AuthorizationCanceled(
        address indexed authorizer,
        bytes32 indexed nonce
    );

    // constant typehashes
    // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
    bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
        0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;

    // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
    bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH =
        0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;

    // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
    bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH =
        0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;

    // errors
    error AuthorizationReused();
    error CallerMustBeThePayee();
    error AuthorizationIsNotYetValid();
    error AuthorizationExpired();
    /**
     * @dev authorizer address => nonce => state (true = used / false = unused)
     */
    mapping(address => mapping(bytes32 => bool)) internal _authorizationStates;

    // viewers

    /**
     * @notice Returns the state of an authorization
     * @dev Nonces are randomly generated 32-byte data unique to the authorizer's
     * address
     * @param authorizer    Authorizer's address
     * @param nonce         Nonce of the authorization
     * @return True if the nonce is used
     */
    function authorizationState(address authorizer, bytes32 nonce)
        external
        view
        returns (bool)
    {
        return _authorizationStates[authorizer][nonce];
    }

    // functions

    /**
     * @notice Execute a transfer with a signed authorization
     * @param from          Payer's address (Authorizer)
     * @param to            Payee's address
     * @param value         Amount to be transferred
     * @param validAfter    The time after which this is valid (unix time)
     * @param validBefore   The time before which this is valid (unix time)
     * @param nonce         Unique nonce
     * @param v             v of the signature
     * @param r             r of the signature
     * @param s             s of the signature
     */
    function transferWithAuthorization(
        address from,
        address to,
        uint256 value,
        uint256 validAfter,
        uint256 validBefore,
        bytes32 nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        _transferWithAuthorization(
            TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
            from,
            to,
            value,
            validAfter,
            validBefore,
            nonce,
            v,
            r,
            s
        );
    }

    /**
     * @notice Receive a transfer with a signed authorization from the payer
     * @dev This has an additional check to ensure that the payee's address matches
     * the caller of this function to prevent front-running attacks. (See security
     * considerations)
     * @param from          Payer's address (Authorizer)
     * @param to            Payee's address
     * @param value         Amount to be transferred
     * @param validAfter    The time after which this is valid (unix time)
     * @param validBefore   The time before which this is valid (unix time)
     * @param nonce         Unique nonce
     * @param v             v of the signature
     * @param r             r of the signature
     * @param s             s of the signature
     */
    function receiveWithAuthorization(
        address from,
        address to,
        uint256 value,
        uint256 validAfter,
        uint256 validBefore,
        bytes32 nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        if (to != msg.sender) revert CallerMustBeThePayee();

        _transferWithAuthorization(
            RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
            from,
            to,
            value,
            validAfter,
            validBefore,
            nonce,
            v,
            r,
            s
        );
    }

    function _transferWithAuthorization(
        bytes32 typeHash,
        address from,
        address to,
        uint256 value,
        uint256 validAfter,
        uint256 validBefore,
        bytes32 nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 timeNow = block.timestamp;

        if (timeNow < validAfter) revert AuthorizationIsNotYetValid();
        if (timeNow > validBefore) revert AuthorizationExpired();
        if (_authorizationStates[from][nonce]) revert AuthorizationReused();

        bytes memory data = abi.encode(
            typeHash,
            from,
            to,
            value,
            validAfter,
            validBefore,
            nonce
        );
        if (EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) != from)
            revert InvalidSignature();

        _authorizationStates[from][nonce] = true;
        emit AuthorizationUsed(from, nonce);

        _transfer(from, to, value);
    }

    /**
     * @notice Attempt to cancel an authorization
     * @param authorizer    Authorizer's address
     * @param nonce         Nonce of the authorization
     * @param v             v of the signature
     * @param r             r of the signature
     * @param s             s of the signature
     */
    function cancelAuthorization(
        address authorizer,
        bytes32 nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        if (_authorizationStates[authorizer][nonce])
            revert AuthorizationReused();

        bytes memory data = abi.encode(
            CANCEL_AUTHORIZATION_TYPEHASH,
            authorizer,
            nonce
        );
        if (EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) != authorizer)
            revert InvalidSignature();

        _authorizationStates[authorizer][nonce] = true;
        emit AuthorizationCanceled(authorizer, nonce);
    }
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT
// Following https://eips.ethereum.org/EIPS/eip-2612
// based on https://github.com/CoinbaseStablecoin/eip-3009

pragma solidity ^0.8.0;

////import "./ERC20Internal.sol";
////import "./EIP712.sol";

/**
    EIP2612 implementation
 */
abstract contract ERC2612 is ERC20Internal, EIP712Domain {
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
    bytes32 public constant PERMIT_TYPEHASH =
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    mapping(address => uint256) internal _nonces;

    error PermitExpired();

    /**
     * @notice Nonces for permit
     * @param owner Token owner's address
     * @return Next nonce
     */
    function nonces(address owner) external view returns (uint256) {
        return _nonces[owner];
    }

    /**
     * @notice update allowance with a signed permit
     * @param owner     Token owner's address (Authorizer)
     * @param spender   Spender's address
     * @param value     Amount of allowance
     * @param deadline  The time at which this expires (unix time)
     * @param v         v of the signature
     * @param r         r of the signature
     * @param s         s of the signature
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        if (deadline < block.timestamp) revert PermitExpired();

        bytes memory data = abi.encode(
            PERMIT_TYPEHASH,
            owner,
            spender,
            value,
            _nonces[owner]++,
            deadline
        );
        if (EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) != owner)
            revert InvalidSignature();

        _approve(owner, spender, value);
    }
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT

pragma solidity ^0.8.0;

////import "./IERC20.sol";
////import "./Ownable.sol";

/**
    ERC20 token and native coin recovery functions
 */
abstract contract Recoverable is Ownable {
    error NothingToRecover();

    /// Recover native coin from contract
    function recoverETH() external onlyOwner {
        uint256 amt = address(this).balance;
        if (amt == 0) revert NothingToRecover();
        payable(owner).transfer(amt);
    }

    /// Recover ERC20 token from contract
    function recoverERC20(address token) external virtual onlyOwner {
        uint256 amt = IERC20(token).balanceOf(address(this));
        if (amt == 0) revert NothingToRecover();
        IERC20(token).transfer(owner, amt);
    }
}

/**
 *  SourceUnit: cypherdog/contracts/CypherDogToken.sol
 */

////// SPDX-License-Identifier-FLATTEN-SUPPRESS-WARNING: MIT
pragma solidity ^0.8.10;

////import "./IERC20.sol";
////import "./Recovery.sol";
////import "./ERC20Internal.sol";
////import "./ERC2612.sol";
////import "./ERC3009.sol";

contract CypherDogToken is
    IERC20,
    ERC20Internal,
    Recoverable,
    ERC2612,
    ERC3009
{
    /**
        part of transaction fee that will be burned until reach minTotalSupply
     */
    uint256 public burnFees = 5;

    /**
        Part of transaction fee that is distributed among hodlers
    */
    uint256 public constant rewardFees = 5;

    /**
        Minimum total supply, when reached burning stops
    */
    uint256 public immutable minTotalSupply;

    struct Summary {
        uint256 totalExcluded; // total held by excluded accounts
        uint256 totalHolding; // total held by holder accounts
        uint256 totalRewards; // total rewards
        uint256 totalSupply; // total supply
    }

    // metadata

    string public constant override name = "CYPHER.DOG";
    string public constant override symbol = "CDOG";
    uint8 public constant override decimals = 18;

    Summary public summary;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    mapping(address => bool) private _excluded;

    // events

    /**
     * @dev Emitted when fees are updated
     * @param burnFees burn fees
     * @param rewardFees rewards fees
     */
    event FeesUpdated(uint256 burnFees, uint256 rewardFees);

    /**
     * @dev Emitted when account is excluded
     * @param account account address
     */
    event AccountExcluded(address indexed account);

    /**
     * @dev Emitted when total rewards amount is updated
     * @param totalRewards total rewards amount
     */
    event TotalRewardsUpdated(uint256 totalRewards);

    //
    // error messages
    //
    error OnlyExcludedCanBurn();
    error ZeroAddressDisallowed();
    error FeesToHigh();
    error AllowanceToLow();
    error NothingToChange();
    error BalanceToLow();
    error MinimumSupplyReached();

    //
    // modifiers
    //
    modifier notZeroAddress(address user) {
        if (user == address(0)) revert ZeroAddressDisallowed();
        _;
    }

    /**
      @dev Initializes the contract
      @param _minTotalSupply min total supply
      @param _supply total supply of tokens minted to owner
     */
    constructor(
        uint256 _minTotalSupply,
        uint256 _supply,
        address[] memory _excluded_
    ) {
        minTotalSupply = _minTotalSupply;

        // excludes owner account
        _excluded[msg.sender] = true;
        emit AccountExcluded(msg.sender);

        // mint supply
        _balances[msg.sender] = _supply;
        summary.totalExcluded = _supply;
        summary.totalSupply = _supply;
        emit Transfer(address(0), msg.sender, _supply);

        // adds predefined excluded accounts
        uint256 _excludedLen = _excluded_.length;
        uint256 index;
        for (index; index < _excludedLen; index++) {
            address user = _excluded_[index];
            _excluded[user] = true;
            emit AccountExcluded(user);
        }
        // initialize EIP712
        uint256 chainId = block.chainid;
        DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(name, "1", chainId);
        CHAINID = chainId;
        EIP712_DOMAIN_TYPEHASH = EIP712.EIP712_DOMAIN_TYPEHASH;
    }

    //
    // external functions
    //

    /**
     * @dev Excludes account from paying fees
     * @param account account address
     */
    function excludeAccount(address account) external onlyOwner {
        if (_excluded[account]) revert NothingToChange();

        uint256 balance = _balances[account];
        if (balance > 0) {
            // we need to kind of _transferToExcludedAccount
            uint256 toExcluded = balance;
            if (summary.totalRewards != 0) {
                uint256 rewards = _calcRewards(account);
                toExcluded += rewards;
                _balances[account] += rewards;
            }
            summary.totalExcluded += toExcluded;
            summary.totalHolding -= balance;
        }

        _excluded[account] = true;

        emit AccountExcluded(account);
    }

    /**
     * @dev Approve spending limit
     * @param spender spender address
     * @param amount spending limit
     */
    function approve(address spender, uint256 amount)
        external
        override
        returns (bool)
    {
        _approve(msg.sender, spender, amount);

        return true;
    }

    /**
     * @dev Burns tokens from msg.sender
     * @param amount tokens amount
     */
    function burn(uint256 amount) external {
        if (!_excluded[msg.sender]) revert OnlyExcludedCanBurn();
        //ERC20 allows to send 0, so we allow to burn 0
        if (amount > 0) {
            if (_balances[msg.sender] < amount) revert BalanceToLow();

            summary.totalSupply -= amount;
            _balances[msg.sender] -= amount;

            summary.totalExcluded -= amount;
        }
        emit Transfer(msg.sender, address(0), amount);
    }

    /**
     * @dev Transfers tokens to recipient
     * @param recipient recipient address
     * @param amount tokens amount
     */
    function transfer(address recipient, uint256 amount)
        external
        override
        returns (bool)
    {
        _transfer(msg.sender, recipient, amount);

        return true;
    }

    /**
     * @dev Transfers tokens from sender to recipient
     * @param sender sender address
     * @param recipient recipient address
     * @param amount tokens amount
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external override returns (bool) {
        uint256 _allowance = _allowances[sender][msg.sender];
        if (_allowance < amount) revert AllowanceToLow();
        if (_allowance < type(uint256).max) {
            unchecked {
                _allowance -= amount;
            }
            _allowances[sender][msg.sender] = _allowance;
        }

        _transfer(sender, recipient, amount);
        return true;
    }

    //
    // external functions (views)
    //

    /**
     * @dev Gets excluded account
     * @param account account address
     */
    function getExcludedAccount(address account) external view returns (bool) {
        return (_excluded[account]);
    }

    /**
     * @dev Gets total supply
     * @return total supply
     */
    function totalSupply() external view override returns (uint256) {
        return summary.totalSupply;
    }

    /**
     * @dev Gets allowance
     * @param sender address
     * @param spender address
     * @return allowance
     */
    function allowance(address sender, address spender)
        external
        view
        override
        returns (uint256)
    {
        return _allowances[sender][spender];
    }

    /**
     * @dev Gets balance of
     * @param account account address
     * @return result account balance
     */
    function balanceOf(address account)
        external
        view
        override
        returns (uint256)
    {
        return _balances[account] + _calcRewards(account);
    }

    /**
     * @dev Gets balance summary
     * @param account account address
     */
    function getBalanceSummary(address account)
        external
        view
        returns (
            uint256 totalBalance,
            uint256 holdingBalance,
            uint256 totalRewards
        )
    {
        holdingBalance = _balances[account];
        totalRewards = _calcRewards(account);
        totalBalance = holdingBalance + totalRewards;

        return (totalBalance, holdingBalance, totalRewards);
    }

    //
    // private functions
    //

    function _approve(
        address sender,
        address spender,
        uint256 amount
    ) internal override notZeroAddress(spender) {
        _allowances[sender][spender] = amount;

        emit Approval(sender, spender, amount);
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override notZeroAddress(sender) notZeroAddress(recipient) {
        //ERC20 allows to send 0 tokens
        if (amount > 0) {
            if (_excluded[sender]) {
                if (_excluded[recipient]) {
                    _transferBetweenExcludedAccounts(sender, recipient, amount);
                } else _transferFromExcludedAccount(sender, recipient, amount);
            } else if (_excluded[recipient]) {
                _transferToExcludedAccount(sender, recipient, amount);
            } else _transferBetweenHolderAccounts(sender, recipient, amount);
        } else emit Transfer(sender, recipient, amount);
    }

    function _transferBetweenHolderAccounts(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        uint256 senderAmount;
        uint256 recipientAmount = amount;
        uint256 burnFee;
        uint256 totalFee;
        Summary memory s = summary;
        uint256 totalSupply_ = s.totalSupply;

        (totalFee, burnFee) = _calcTransferFees(amount);

        (totalSupply_, totalFee, burnFee) = _matchTotalSupplyWithFees(
            totalSupply_,
            totalFee,
            burnFee
        );

        senderAmount = amount + totalFee;
        // appends total rewards
        if (s.totalRewards != 0) {
            uint256 inclRewards = s.totalHolding + s.totalRewards;
            unchecked {
                senderAmount = (senderAmount * s.totalHolding) / inclRewards;
                recipientAmount =
                    (recipientAmount * s.totalHolding) /
                    inclRewards;
                totalFee = (totalFee * s.totalHolding) / inclRewards;
            }
        }
        uint256 currSender = _balances[sender];
        if (currSender < senderAmount) revert BalanceToLow();
        unchecked {
            _balances[sender] = currSender - senderAmount;
            _balances[recipient] += recipientAmount;

            summary.totalSupply = totalSupply_;
            summary.totalHolding = s.totalHolding - totalFee;
        }
        emit Transfer(sender, recipient, amount);
        if (burnFee > 0) {
            emit Transfer(sender, address(0), burnFee);
        }

        _updateTotalRewards();
    }

    function _transferFromExcludedAccount(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        uint256 currSender = _balances[sender];
        if (currSender < amount) revert BalanceToLow();
        unchecked {
            _balances[sender] = currSender - amount;
            _balances[recipient] += amount;

            summary.totalExcluded -= amount;
            summary.totalHolding += amount;
        }
        emit Transfer(sender, recipient, amount);

        _updateTotalRewards();
    }

    function _transferToExcludedAccount(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        uint256 totalFee;
        uint256 burnFee;
        Summary memory s = summary;
        uint256 totalSupply_ = s.totalSupply;

        (totalFee, burnFee) = _calcTransferFees(amount);

        (totalSupply_, totalFee, burnFee) = _matchTotalSupplyWithFees(
            totalSupply_,
            totalFee,
            burnFee
        );

        uint256 senderAmount = amount + totalFee;

        // append total rewards
        if (s.totalRewards != 0) {
            uint256 totalHolding = s.totalHolding;
            unchecked {
                senderAmount =
                    (senderAmount * totalHolding) /
                    (totalHolding + s.totalRewards);
            }
        }
        uint256 currSender = _balances[sender];
        if (currSender < senderAmount) revert BalanceToLow();
        unchecked {
            _balances[sender] = currSender - senderAmount;
            _balances[recipient] += amount;

            summary.totalSupply = totalSupply_;
            summary.totalExcluded = s.totalExcluded + amount;
            summary.totalHolding = s.totalHolding - senderAmount;
        }
        emit Transfer(sender, recipient, amount);
        if (burnFee > 0) {
            emit Transfer(sender, address(0), burnFee);
        }
        _updateTotalRewards();
    }

    function _transferBetweenExcludedAccounts(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        uint256 currSender = _balances[sender];
        if (currSender < amount) revert BalanceToLow();
        unchecked {
            _balances[sender] = currSender - amount;
            _balances[recipient] += amount;
        }
        emit Transfer(sender, recipient, amount);
    }

    function _calcTransferFees(uint256 amount)
        private
        view
        returns (uint256 totalFee, uint256 burnFee)
    {
        burnFee = _percent(amount, burnFees);
        totalFee = _percent(amount, rewardFees) + burnFee;
    }

    function _updateTotalRewards() private {
        // totalRewards = totalSupply - totalExcluded - totalHolding
        uint256 totalRewards = summary.totalSupply -
            summary.totalExcluded -
            summary.totalHolding;

        if (totalRewards != summary.totalRewards) {
            summary.totalRewards = totalRewards;

            emit TotalRewardsUpdated(totalRewards);
        }
    }

    function _matchTotalSupplyWithFees(
        uint256 totalSupply_,
        uint256 totalFee,
        uint256 burnFee
    )
        private
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        if (burnFee != 0) {
            uint256 newTotalSupply = totalSupply_ - burnFee;

            if (newTotalSupply >= minTotalSupply) {
                totalSupply_ = newTotalSupply;
            } else {
                // turn off burn fee, this will happen only once
                burnFees = 0;
                // are we still over minimum at start?
                if (totalSupply_ > minTotalSupply) {
                    totalSupply_ = minTotalSupply;
                    // refund burn fee difference
                    uint256 diff = minTotalSupply - newTotalSupply;
                    burnFee -= diff;
                    totalFee += diff;
                } else {
                    // it was already under minimum - refund burn fee
                    totalFee -= burnFee;
                    burnFee = 0;
                }
            }
        }

        return (totalSupply_, totalFee, burnFee);
    }

    //
    // private functions (views)
    //

    function _calcRewards(address account)
        private
        view
        returns (uint256 result)
    {
        Summary memory s = summary;
        if (
            !_excluded[account] && s.totalRewards != 0 // only for holders
        ) {
            result = (s.totalRewards * _balances[account]) / s.totalHolding;
        }
    }

    //
    // private functions (pure)
    //

    /**
        Calculate given percent (1/100) of amount.
        @param amount to divide
        @param pct percent to calculate
        @return result caluclated value
     */
    function _percent(uint256 amount, uint256 pct)
        internal
        pure
        returns (uint256 result)
    {
        if (pct == 0 || amount == 0) {
            return result;
        }
        result = (amount * pct) / 100;
    }
}

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

Context size (optional):