The KRILL token contract is built using the UUPS proxy pattern, allowing the contract logic to be upgraded while maintaining the state. This pattern leverages the ERC1967Proxy from OpenZeppelin to ensure compatibility and security.
The initialize function is called only once to set up the token. This function initializes key parameters such as the token name, symbol, initial supply, airdrop configuration, and balance limits.
Minting Tokens
The mint function allows the contract owner to mint tokens to specific addresses while respecting the balance limit.
Users can claim an airdrop of tokens using the airdrop function, provided they have not exceeded the allowed balance or daily limit, and sufficient time has passed since their last airdrop.
functionairdrop() public
Enforcing Transfer Limits
The _enforceTransferLimits internal function is called before any token transfer to ensure that the recipient does not exceed the daily receive or balance limits.
To improve gas efficiency and clarity, the following custom errors are used in the contract:
ContractDoesNotAcceptEther(): Reverts if Ether is sent to the contract.
MaxBalanceExceeded(currentBalance, maxBalance): Reverts if the transfer or mint would exceed the recipient's maximum balance.
MaxDailyReceiveExceeded(dailyReceived, maxDailyReceive): Reverts if the recipient exceeds the daily receive limit.
AccountIsFrozen(account): Reverts if the operation is attempted on a frozen account.
Contract Configuration
The following functions allow the owner to modify key parameters of the contract:
setAirdropAmount(uint256 newAirdropAmount): Updates the airdrop amount.
setAirdropInterval(uint256 newAirdropInterval): Updates the time interval between airdrops.
setMaxBalance(uint256 newMaxBalance): Updates the maximum token balance per account.
setMaxDailyReceive(uint256 newMaxDailyReceive): Updates the maximum tokens an account can receive per day.
Events
The contract emits the following events to notify key actions:
Frozen(address indexed account): When an account is frozen.
Unfrozen(address indexed account): When an account is unfrozen.
Airdrop(address indexed recipient, uint256 amount): When an airdrop is executed.
Minted(address indexed to, uint256 amount): When tokens are minted.
Upgradeability
The KRILL token contract is upgradeable, following the UUPS proxy pattern. Only the contract owner can authorize upgrades.
Token Configuration Details
Airdrop Amount: Configurable by the owner.
Airdrop Interval: Time between two airdrops (in seconds).
Max Balance: Maximum allowable balance for an account.
Max Daily Receive: Maximum tokens an account can receive in one day.
// SPDX-License-Identifier: MIT/** * @dev Important Notice: This token is fully centralized and does not carry any financial or investment value. * The primary purpose of this token is utility within the Whale in the Box ecosystem, and it should not be considered a tradable asset.
* Any attempts to create a liquidity pool, facilitate decentralized trading, or assign financial value to this token are
* strictly against its intended use and logic. The token is managed centrally, and no financial expectations should be associated with it.
*/pragmasolidity 0.8.27;import"@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";import"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";import"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";import"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";// Custom errors for better gas efficiency and clarity.errorContractDoesNotAcceptEther(); // Reverts if Ether is sent to this contract.error MaxBalanceExceeded(uint256 currentBalance, uint256 maxBalance); // Reverts if a transfer or mint would exceed the max allowed balance.
error MaxDailyReceiveExceeded(uint256 dailyReceived, uint256 maxDailyReceive); // Reverts if an account exceeds its daily receive limit.
errorAccountIsFrozen(address account); // Reverts if an operation is attempted on a frozen account.error MintAmountExceedsLimit(uint256 requestedAmount, uint256 maxAllowed); // Reverts if mint exceeds the allowed limit per balance.
error AirdropIntervalNotReached(uint256 lastAirdropTime, uint256 requiredNextAirdropTime); // Reverts if an airdrop is requested before the interval is met.
errorCannotOperateOnZeroAddress(); // Reverts if a zero address is used in operations./** * @title KRILL Token * @dev This contract represents an ERC20 token with added functionalities such as airdrops, account freezing, and balance/daily receive limits.
* The contract is upgradeable using the UUPS proxy pattern, and fully centralized with no financial or speculative purpose.
*/contractKRILLisInitializable, ERC20Upgradeable, Ownable2StepUpgradeable, UUPSUpgradeable {// State variables for the token's parameters, defined by the owner.uint256private _airdropAmount; // Amount of tokens given during airdropuint256private _airdropInterval; // Time interval required between airdrops (in seconds)uint256private _maxBalance; // Maximum balance allowed per accountuint256private _maxDailyReceive; // Maximum daily receive limit per account/** * @dev Stores data related to an account's last airdrop and freeze status. */structAccountData {uint256 lastAirdropTime; // Last time the account received an airdropbool isFrozen; // If true, the account is frozen and cannot transfer or receive tokens }/** * @dev Tracks how many tokens an account has received in a day and when the last receive action occurred. */structReceiveData {uint256 dailyReceived; // Total tokens received in the current dayuint256 lastReceivedDay; // The day (in block timestamp) of the last receive action }// Mapping to track account-specific data (e.g., freeze status and last airdrop time)mapping(address=> AccountData) private _accounts;// Mapping to track daily receive data per accountmapping(address=> ReceiveData) private _receiveData;// Events emitted during key actions in the contracteventFrozen(addressindexed account); // When an account is frozeneventUnfrozen(addressindexed account); // When an account is unfrozeneventBurned(addressindexed account, uint256 amount); // When tokens are burned from an accounteventAirdrop(addressindexed recipient, uint256 amount); // When an airdrop is executedeventMinted(addressindexed to, uint256 amount); // When tokens are mintedeventAirdropAmountUpdated(uint256 newAmount); // When the airdrop amount is updatedeventAirdropIntervalUpdated(uint256 newInterval); // When the airdrop interval is updatedeventMaxBalanceUpdated(uint256 newMaxBalance); // When the maximum balance is updatedeventMaxDailyReceiveUpdated(uint256 newMaxDailyReceive); // When the daily receive limit is updated/// @custom:oz-upgrades-unsafe-allow constructorconstructor() {_disableInitializers(); }/** * @dev Initializes the contract with token details, owner, and configuration parameters. * @param name_ Name of the token. * @param symbol_ Symbol representing the token. * @param initialSupply_ Initial supply of tokens that will be minted to the owner. * @param owner_ Address that will become the contract owner. * @param airdropAmount_ Amount of tokens distributed per airdrop. * @param airdropInterval_ Time interval between airdrops, specified in seconds. * @param maxBalance_ Maximum token balance allowed per account. * @param maxDailyReceive_ Maximum tokens an account can receive in a single day. */functioninitialize(stringmemory name_,stringmemory symbol_,uint256 initialSupply_,address owner_,uint256 airdropAmount_,uint256 airdropInterval_,uint256 maxBalance_,uint256 maxDailyReceive_ ) publicinitializer {__ERC20_init(name_, symbol_);__Ownable2Step_init();__UUPSUpgradeable_init();_transferOwnership(owner_);_mint(owner_, initialSupply_); // Mint initial supply to owner// Set configurable parameters _airdropAmount = airdropAmount_; _airdropInterval = airdropInterval_; _maxBalance = maxBalance_; _maxDailyReceive = maxDailyReceive_; }/** * @dev Mints a specified amount of tokens to a given address, while ensuring that the maximum * balance limit is respected. If the minting would result in the balance exceeding the * `_maxBalance`, only up to the max balance is minted. * * @param to The address to which tokens will be minted. * @param amount The amount of tokens to mint. * * Requirements: * - The `to` address cannot be the zero address. * - The `to` account must not be frozen. */functionmint(address to,uint256 amount) publiconlyOwner {if (to ==address(0)) revertCannotOperateOnZeroAddress(); // Ensure the recipient is not the zero address.if (_accounts[to].isFrozen) revertAccountIsFrozen(to); // Prevent minting to frozen accounts.uint256 toBalance =balanceOf(to); // Fetch the current balance of the recipient. uint256 maxMintAmount = _maxBalance - toBalance; // Calculate the maximum allowed mint amount without exceeding the balance limit.
// If minting would exceed the max balance, mint only up to the limit.if (toBalance + amount > _maxBalance) {_mint(to, maxMintAmount);emitMinted(to, maxMintAmount); } else {_mint(to, amount);emitMinted(to, amount); } }/** * @dev Allows a user to claim an airdrop. Users can claim an airdrop once every set interval. * Requirements: * - The user's account must not be frozen. * - The user must not exceed their maximum allowed balance after receiving the airdrop. * - The user must wait the full airdrop interval between consecutive airdrops. */functionairdrop() public { AccountData storage account = _accounts[msg.sender];uint256 lastTime = account.lastAirdropTime;uint256 nextAirdropTime = lastTime + _airdropInterval;if (block.timestamp < nextAirdropTime) revertAirdropIntervalNotReached(lastTime, nextAirdropTime);if (account.isFrozen) revertAccountIsFrozen(msg.sender); account.lastAirdropTime = block.timestamp;uint256 toBalance =balanceOf(msg.sender);uint256 maxMintAmount = _maxBalance - toBalance;uint256 mintAmount = _airdropAmount > maxMintAmount ? maxMintAmount : _airdropAmount;if (mintAmount ==0) revertMaxBalanceExceeded(toBalance, _maxBalance);_mint(msg.sender, mintAmount);emitAirdrop(msg.sender, mintAmount); }/** * @dev Allows the owner to freeze a specified account, preventing it from transferring or receiving tokens. * @param account Address of the account to freeze. */functionfreeze(address account) publiconlyOwner {if (_accounts[account].isFrozen) revertAccountIsFrozen(account); _accounts[account].isFrozen =true;emitFrozen(account); }/** * @dev Allows the owner to unfreeze a specified account. * @param account Address of the account to unfreeze. */functionunfreeze(address account) publiconlyOwner {if (!_accounts[account].isFrozen) revert("Account is not frozen"); _accounts[account].isFrozen =false;emitUnfrozen(account); }/** * @dev Internal function that enforces transfer rules (max balance, daily limits) before token transfers. * This function is called automatically before any transfer, including minting and burning. * @param from Address sending the tokens. * @param to Address receiving the tokens. * @param amount Amount of tokens being transferred. */function_enforceTransferLimits(address from,address to,uint256 amount ) internalvirtual {if (from !=address(0)) {if (_accounts[from].isFrozen) revertAccountIsFrozen(from); }if (to !=address(0)) {if (_accounts[to].isFrozen) revertAccountIsFrozen(to); ReceiveData storage receiveData = _receiveData[to];uint256 currentDay = block.timestamp /1days;// Reset daily receive data if a new day starts.if (receiveData.lastReceivedDay < currentDay) { receiveData.dailyReceived = amount; receiveData.lastReceivedDay = currentDay; } else { receiveData.dailyReceived += amount; }// Check daily receive limit.if (receiveData.dailyReceived > _maxDailyReceive) {revertMaxDailyReceiveExceeded(receiveData.dailyReceived, _maxDailyReceive); }uint256 toBalance =balanceOf(to);// Check max balance limit.if (toBalance + amount > _maxBalance) {revertMaxBalanceExceeded(toBalance, _maxBalance); } } }/** * @dev Sets a new airdrop amount. * @param newAirdropAmount The new amount for the airdrop. */functionsetAirdropAmount(uint256 newAirdropAmount) publiconlyOwner { _airdropAmount = newAirdropAmount;emitAirdropAmountUpdated(newAirdropAmount); }/** * @dev Sets a new airdrop interval. * @param newAirdropInterval The new interval for airdrops in seconds. */functionsetAirdropInterval(uint256 newAirdropInterval) publiconlyOwner { _airdropInterval = newAirdropInterval;emitAirdropIntervalUpdated(newAirdropInterval); }/** * @dev Sets a new maximum balance for accounts. * @param newMaxBalance The new maximum balance allowed per account. */functionsetMaxBalance(uint256 newMaxBalance) publiconlyOwner { _maxBalance = newMaxBalance;emitMaxBalanceUpdated(newMaxBalance); }/** * @dev Sets a new daily receive limit. * @param newMaxDailyReceive The new maximum amount of tokens an account can receive per day. */functionsetMaxDailyReceive(uint256 newMaxDailyReceive) publiconlyOwner { _maxDailyReceive = newMaxDailyReceive;emitMaxDailyReceiveUpdated(newMaxDailyReceive); }/** * @dev Prevents the contract from accepting Ether. This contract only deals with the KRILL token. */receive() externalpayable {revertContractDoesNotAcceptEther(); }/** * @dev Returns the current airdrop amount. */functiongetAirdropAmount() publicviewreturns (uint256) {return _airdropAmount; }/** * @dev Returns the current airdrop interval in seconds. */functiongetAirdropInterval() publicviewreturns (uint256) {return _airdropInterval; }/** * @dev Returns the current maximum balance allowed per account. */functiongetMaxBalance() publicviewreturns (uint256) {return _maxBalance; }/** * @dev Returns the current maximum daily receive limit per account. */functiongetMaxDailyReceive() publicviewreturns (uint256) {return _maxDailyReceive; }/** * @dev Function that authorizes contract upgrades. Restricted to the contract owner. */function_authorizeUpgrade(address newImplementation) internaloverrideonlyOwner {}/** * @dev Returns the current version of the contract. */functionversion() publicpurereturns (stringmemory) {return"v1.1"; }}
// SPDX-License-Identifier: MIT/** * @title KRILL Proxy Contract * @dev This contract implements a UUPS proxy for the KRILLToken contract. * It allows upgrading the implementation of the contract while preserving the stored state. *//** * @dev Important Notice: This token is fully centralized and does not carry any financial or investment value. * The primary purpose of this token is utility within the Whale in the Box ecosystem, and it should not be considered a tradable asset.
* Any attempts to create a liquidity pool, facilitate decentralized trading, or assign financial value to this token are
* strictly against its intended use and logic. The token is managed centrally, and no financial expectations should be associated with it.
*/pragmasolidity 0.8.27;import"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";/** * @dev Proxy contract for KRILLToken using the UUPS pattern. */contractKRILLProxyisERC1967Proxy {/** * @dev Initializes the proxy with an initial implementation specified by `_logic` and * optional initialization data `_data` to be passed as a delegatecall to the implementation. * @param _logic Address of the initial implementation. * @param _data Data to send as msg.data in the low level call. */constructor(address_logic,bytesmemory_data) payable ERC1967Proxy(_logic, _data) {}/** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */uint256[50] private __gap;/** * @dev Fallback function allowing to perform a delegatecall to the given implementation. * This function will return whatever the implementation call returns. */fallback() externalpayablevirtualoverride {_fallback(); }/** * @dev Receive function allowing to perform a delegatecall to the given implementation. * This function will return whatever the implementation call returns. */receive() externalpayablevirtual {_fallback(); }}