harb/onchain/src/Harberg.sol
2025-07-08 10:33:10 +02:00

136 lines
5.7 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.
*/
contract Harberg is ERC20, ERC20Permit {
using Math for uint256;
// Minimum fraction of the total supply required for staking to prevent fragmentation of staking positions
uint256 private constant MIN_STAKE_FRACTION = 3000;
// 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 Harberg 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_) {}
/**
* @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 {
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 {
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 HARB 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);
}
}