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;
}
}