// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import { ERC20 } from "@openzeppelin/token/ERC20/ERC20.sol"; import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol"; import { Math } from "@openzeppelin/utils/math/Math.sol"; /** * @title stakeable ERC20 Token * @notice This contract implements an ERC20 token with mechanisms for minting and burning in which a single account (staking Pool) is proportionally receiving a share. Only the liquidity manager has permission to manage token supply. * @dev Key features: * - Controlled minting exclusively by LiquidityManager * - Tax collection and redistribution mechanism through staking pool * - 20% supply cap for staking (20,000 positions max) * - Staking pool receives proportional share of all mints/burns */ contract Kraiken is ERC20, ERC20Permit { using Math for uint256; /** * @notice Protocol version for data structure compatibility. * Increment when making breaking changes to TAX_RATES, events, or core data structures. * Indexers and frontends validate against this to ensure sync. * * Version History: * - v1: Initial deployment with 30-tier TAX_RATES * - v2: OptimizerV3, VWAP mirror floor, directional VWAP recording */ uint256 public constant VERSION = 2; // Minimum fraction of the total supply required for staking to prevent fragmentation of staking positions uint256 private constant MIN_STAKE_FRACTION = 3000; // Address authorized to call one-time setters (prevents frontrunning) address private immutable deployer; // Address of the liquidity manager address private liquidityManager; // Address of the staking pool address private stakingPool; // Previous total supply for staking calculations uint256 public previousTotalSupply; // Custom errors error ZeroAddressInSetter(); error AddressAlreadySet(); // Modifier to restrict access to the liquidity manager modifier onlyLiquidityManager() { require(msg.sender == address(liquidityManager), "only liquidity manager"); _; } /** * @notice Constructor for the Kraiken token * @param name_ The name of the token * @param symbol_ The symbol of the token */ constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) ERC20Permit(name_) { deployer = msg.sender; } /** * @notice Sets the address for the liquidityManager. Used once post-deployment to initialize the contract. * @dev Should be called only once right after the contract deployment to set the liquidity manager address. * Throws AddressAlreadySet if called more than once. * @param liquidityManager_ The address of the liquidity manager. */ function setLiquidityManager(address liquidityManager_) external { require(msg.sender == deployer, "only deployer"); if (address(0) == liquidityManager_) revert ZeroAddressInSetter(); if (liquidityManager != address(0)) revert AddressAlreadySet(); liquidityManager = liquidityManager_; } /** * @notice Sets the address for the stakingPool. Used once post-deployment to initialize the contract. * @dev Should be called only once right after the contract deployment to set the staking pool address. * Throws AddressAlreadySet if called more than once. * @param stakingPool_ The address of the staking pool. */ function setStakingPool(address stakingPool_) external { require(msg.sender == deployer, "only deployer"); if (address(0) == stakingPool_) revert ZeroAddressInSetter(); if (stakingPool != address(0)) revert AddressAlreadySet(); stakingPool = stakingPool_; } /** * @notice Returns the addresses of the periphery contracts * @return The addresses of the TWAB controller, liquidity manager, staking pool, and liquidity pool */ function peripheryContracts() external view returns (address, address) { return (liquidityManager, stakingPool); } /** * @notice Calculates the minimum stake based on the previous total supply * @return The minimum stake amount */ function minStake() external view returns (uint256) { return previousTotalSupply / MIN_STAKE_FRACTION; } /** * @notice Allows the liquidity manager to mint tokens for itself. * @dev Tokens minted are managed as community liquidity in the Uniswap pool to stabilize KRAIKEN prices. * Only callable by the Liquidity Manager. Minting rules and limits are defined externally. * @param _amount The number of tokens to mint. */ function mint(uint256 _amount) external onlyLiquidityManager { if (_amount > 0) { // make sure staking pool grows proportional to economy uint256 stakingPoolBalance = balanceOf(stakingPool); if (stakingPoolBalance > 0) { uint256 newStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance); _mint(stakingPool, newStake); } _mint(address(liquidityManager), _amount); } if (previousTotalSupply == 0) { previousTotalSupply = totalSupply(); } } /** * @notice Allows the liquidity manager to burn tokens from its account, adjusting the staking pool accordingly. * @dev When tokens are burned, the total supply shrinks, making excess tokens in the staking pool unnecessary. * These excess tokens are burned to maintain the guaranteed fixed percentage of the total supply for stakers. * @param _amount The number of tokens to burn. */ function burn(uint256 _amount) external onlyLiquidityManager { if (_amount > 0) { // shrink staking pool proportional to economy uint256 stakingPoolBalance = balanceOf(stakingPool); if (stakingPoolBalance > 0) { uint256 excessStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance); _burn(stakingPool, excessStake); } _burn(address(liquidityManager), _amount); } } /** * @notice Sets the previous total supply * @param _ts The previous total supply value */ function setPreviousTotalSupply(uint256 _ts) external onlyLiquidityManager { previousTotalSupply = _ts; } /** * @notice Returns the outstanding supply, excluding the balances of the liquidity pool and liquidity manager * @return The outstanding supply */ function outstandingSupply() public view returns (uint256) { return totalSupply() - balanceOf(liquidityManager); } }