Core protocol changes for launch readiness: - OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18. Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48]. - Directional VWAP: only records prices on ETH inflow (buys), preventing sell-side dilution of price memory - Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses distance from adjusted VWAP as floor distance, no branching - PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s) - Access control (M-2 fix): deployer-only guard on one-time setters - Recenter rate limit (M-3 fix): 60-second cooldown for open recenters - Safe fallback params: recenter() optimizer-failure defaults changed from exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100 - Recentered event for monitoring and indexing - VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
158 lines
6.6 KiB
Solidity
158 lines
6.6 KiB
Solidity
// 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);
|
|
}
|
|
}
|