🍀KRILL Contract

Smart Contract Architecture

Proxy Pattern - UUPS (Universal Upgradeable Proxy Standard)

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.


Contract Addresses

  • KRILL Proxy Contract: 0x33E5b643C05a3B00F71a066FefA4F59eF6BE27fc

https://basescan.org/token/0x33e5b643c05a3b00f71a066fefa4f59ef6be27fc#code

  • KRILL Token Contract: 0x13e5d7E7D544DA28BFD80B76C1D3F32A41974226

https://basescan.org/address/0x13e5d7e7d544da28bfd80b76c1d3f32a41974226#code


Important Notices

  • KRILL is free and has no financial value.


Key Contract Functions

Initialization

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.

function mint(address to, uint256 amount) public onlyOwner
  • to: Address of the recipient.

  • amount: Number of tokens to be minted.

Airdrop Mechanism

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.

function airdrop() 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.

function _enforceTransferLimits(address from, address to, uint256 amount)

Custom Errors for Gas Efficiency

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.
 */

pragma solidity 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.
error ContractDoesNotAcceptEther(); // 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.
error AccountIsFrozen(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.
error CannotOperateOnZeroAddress(); // 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.
 */
contract KRILL is Initializable, ERC20Upgradeable, Ownable2StepUpgradeable, UUPSUpgradeable {
    
    // State variables for the token's parameters, defined by the owner.
    uint256 private _airdropAmount;      // Amount of tokens given during airdrop
    uint256 private _airdropInterval;    // Time interval required between airdrops (in seconds)
    uint256 private _maxBalance;         // Maximum balance allowed per account
    uint256 private _maxDailyReceive;    // Maximum daily receive limit per account
    
    /**
     * @dev Stores data related to an account's last airdrop and freeze status.
     */
    struct AccountData {
        uint256 lastAirdropTime;  // Last time the account received an airdrop
        bool 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.
     */
    struct ReceiveData {
        uint256 dailyReceived;    // Total tokens received in the current day
        uint256 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 account
    mapping(address => ReceiveData) private _receiveData;

    // Events emitted during key actions in the contract
    event Frozen(address indexed account);   // When an account is frozen
    event Unfrozen(address indexed account); // When an account is unfrozen
    event Burned(address indexed account, uint256 amount); // When tokens are burned from an account
    event Airdrop(address indexed recipient, uint256 amount); // When an airdrop is executed
    event Minted(address indexed to, uint256 amount); // When tokens are minted
    event AirdropAmountUpdated(uint256 newAmount); // When the airdrop amount is updated
    event AirdropIntervalUpdated(uint256 newInterval); // When the airdrop interval is updated
    event MaxBalanceUpdated(uint256 newMaxBalance); // When the maximum balance is updated
    event MaxDailyReceiveUpdated(uint256 newMaxDailyReceive); // When the daily receive limit is updated

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _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.
     */
    function initialize(
        string memory name_,
        string memory symbol_,
        uint256 initialSupply_,
        address owner_,
        uint256 airdropAmount_,
        uint256 airdropInterval_,
        uint256 maxBalance_,
        uint256 maxDailyReceive_
    ) public initializer {
        __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.
    */
    function mint(address to, uint256 amount) public onlyOwner {
        if (to == address(0)) revert CannotOperateOnZeroAddress(); // Ensure the recipient is not the zero address.
        if (_accounts[to].isFrozen) revert AccountIsFrozen(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);
            emit Minted(to, maxMintAmount);
        } else {
            _mint(to, amount);
            emit Minted(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.
     */
    function airdrop() public {
        AccountData storage account = _accounts[msg.sender];
        uint256 lastTime = account.lastAirdropTime;
        uint256 nextAirdropTime = lastTime + _airdropInterval;
        
        if (block.timestamp < nextAirdropTime) revert AirdropIntervalNotReached(lastTime, nextAirdropTime);
        if (account.isFrozen) revert AccountIsFrozen(msg.sender);
        
        account.lastAirdropTime = block.timestamp;

        uint256 toBalance = balanceOf(msg.sender);
        uint256 maxMintAmount = _maxBalance - toBalance;
        uint256 mintAmount = _airdropAmount > maxMintAmount ? maxMintAmount : _airdropAmount;

        if (mintAmount == 0) revert MaxBalanceExceeded(toBalance, _maxBalance);

        _mint(msg.sender, mintAmount);
        emit Airdrop(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.
     */
    function freeze(address account) public onlyOwner {
        if (_accounts[account].isFrozen) revert AccountIsFrozen(account);

        _accounts[account].isFrozen = true;
        emit Frozen(account);
    }

    /**
     * @dev Allows the owner to unfreeze a specified account.
     * @param account Address of the account to unfreeze.
     */
    function unfreeze(address account) public onlyOwner {
        if (!_accounts[account].isFrozen) revert("Account is not frozen");

        _accounts[account].isFrozen = false;
        emit Unfrozen(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
    ) internal virtual {
        if (from != address(0)) {
            if (_accounts[from].isFrozen) revert AccountIsFrozen(from);
        }

        if (to != address(0)) {
            if (_accounts[to].isFrozen) revert AccountIsFrozen(to);

            ReceiveData storage receiveData = _receiveData[to];
            uint256 currentDay = block.timestamp / 1 days;

            // 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) {
                revert MaxDailyReceiveExceeded(receiveData.dailyReceived, _maxDailyReceive);
            }

            uint256 toBalance = balanceOf(to);
            // Check max balance limit.
            if (toBalance + amount > _maxBalance) {
                revert MaxBalanceExceeded(toBalance, _maxBalance);
            }
        }
    }

    /**
    * @dev Sets a new airdrop amount.
    * @param newAirdropAmount The new amount for the airdrop.
    */
    function setAirdropAmount(uint256 newAirdropAmount) public onlyOwner {
        _airdropAmount = newAirdropAmount;
        emit AirdropAmountUpdated(newAirdropAmount);
    }

    /**
    * @dev Sets a new airdrop interval.
    * @param newAirdropInterval The new interval for airdrops in seconds.
    */
    function setAirdropInterval(uint256 newAirdropInterval) public onlyOwner {
        _airdropInterval = newAirdropInterval;
        emit AirdropIntervalUpdated(newAirdropInterval);
    }

    /**
    * @dev Sets a new maximum balance for accounts.
    * @param newMaxBalance The new maximum balance allowed per account.
    */
    function setMaxBalance(uint256 newMaxBalance) public onlyOwner {
        _maxBalance = newMaxBalance;
        emit MaxBalanceUpdated(newMaxBalance);
    }

    /**
    * @dev Sets a new daily receive limit.
    * @param newMaxDailyReceive The new maximum amount of tokens an account can receive per day.
    */
    function setMaxDailyReceive(uint256 newMaxDailyReceive) public onlyOwner {
        _maxDailyReceive = newMaxDailyReceive;
        emit MaxDailyReceiveUpdated(newMaxDailyReceive);
    }

    /**
     * @dev Prevents the contract from accepting Ether. This contract only deals with the KRILL token.
     */
    receive() external payable {
        revert ContractDoesNotAcceptEther();
    }

    /**
    * @dev Returns the current airdrop amount.
    */
    function getAirdropAmount() public view returns (uint256) {
        return _airdropAmount;
    }

    /**
    * @dev Returns the current airdrop interval in seconds.
    */
    function getAirdropInterval() public view returns (uint256) {
        return _airdropInterval;
    }

    /**
    * @dev Returns the current maximum balance allowed per account.
    */
    function getMaxBalance() public view returns (uint256) {
        return _maxBalance;
    }

    /**
    * @dev Returns the current maximum daily receive limit per account.
    */
    function getMaxDailyReceive() public view returns (uint256) {
        return _maxDailyReceive;
    }

    /**
     * @dev Function that authorizes contract upgrades. Restricted to the contract owner.
     */
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    /**
     * @dev Returns the current version of the contract.
     */
    function version() public pure returns (string memory) {
        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.
 */

pragma solidity 0.8.27;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

/**
 * @dev Proxy contract for KRILLToken using the UUPS pattern.
 */
contract KRILLProxy is ERC1967Proxy {
    /**
     * @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, bytes memory _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() external payable virtual override {
        _fallback();
    }

    /**
     * @dev Receive function allowing to perform a delegatecall to the given implementation.
     * This function will return whatever the implementation call returns.
     */
    receive() external payable virtual {
        _fallback();
    }
}

Last updated