// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import { ERC20, IERC20, IERC20Metadata } from "@openzeppelin/token/ERC20/ERC20.sol"; import { ERC20Permit, IERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol"; import { SafeCast } from "@openzeppelin/utils/math/SafeCast.sol"; import { IStake } from "./interfaces/IStake.sol"; import { TwabController } from "pt-v5-twab-controller/TwabController.sol"; /** * @title TWAB ERC20 Token * @notice This contract creates an ERC20 token with balances stored in a TwabController, * enabling time-weighted average balances for each holder. * @dev The TwabController limits all balances including total token supply to uint96 for * gas savings. Any mints that increase a balance past this limit will fail. */ contract Harb is ERC20, ERC20Permit { /* ============ Public Variables ============ */ /// @notice Address of the TwabController used to keep track of balances. TwabController public immutable twabController; /// @notice Address of the LiquidityManager contract that mints and burns supply address public liquidityManager; address public stakingPool; /* ============ Errors ============ */ /// @notice Thrown if the some address is unexpectedly the zero address. error ZeroAddressInConstructor(); /// @dev Function modifier to ensure that the caller is the liquidityManager modifier onlyLiquidityManager() { require(msg.sender == liquidityManager, "Harb/only-lm"); _; } /* ============ Constructor ============ */ /** * @notice TwabERC20 Constructor * @param name_ The name of the token * @param symbol_ The token symbol */ constructor( string memory name_, string memory symbol_, TwabController twabController_ ) ERC20(name_, symbol_) ERC20Permit(name_) { if (address(0) == address(twabController_)) revert ZeroAddressInConstructor(); twabController = twabController_; } function setLiquidityManager(address liquidityManager_) external { // TODO: add trapdoor if (address(0) == liquidityManager_) revert ZeroAddressInConstructor(); liquidityManager = liquidityManager_; } function setStakingPool(address stakingPool_) external { // TODO: add trapdoor if (address(0) == stakingPool_) revert ZeroAddressInConstructor(); stakingPool = stakingPool_; } /* ============ External Functions ============ */ /// @notice Allows the liquidityManager to mint tokens for itself /// @dev May be overridden to provide more granular control over minting /// @param _amount Amount of tokens to mint function mint(uint256 _amount) external onlyLiquidityManager { _mintHarb(liquidityManager, _amount); } /// @notice Allows the liquidityManager to burn tokens from a its account /// @dev May be overridden to provide more granular control over burning /// @param _amount Amount of tokens to burn function burn(uint256 _amount) external onlyLiquidityManager { _burnHarb(liquidityManager, _amount); } /* ============ Public ERC20 Overrides ============ */ /// @inheritdoc ERC20 function balanceOf( address _account ) public view virtual override(ERC20) returns (uint256) { return twabController.balanceOf(address(this), _account); } /// @inheritdoc ERC20 function totalSupply() public view virtual override(ERC20) returns (uint256) { return twabController.totalSupply(address(this)); } /* ============ Internal ERC20 Overrides ============ */ /** * @notice Mints tokens to `_receiver` and increases the total supply. * @dev Emits a {Transfer} event with `from` set to the zero address. * @dev `receiver` cannot be the zero address. * @param receiver Address that will receive the minted tokens * @param amount Tokens to mint */ function _mintHarb(address receiver, uint256 amount) internal { // make sure staking pool grows proportional to economy uint256 stakingPoolBalance = balanceOf(stakingPool); uint256 activeSupply = totalSupply - stakingPoolBalance; uint256 dormantStake = IStake(stakingPool).dormantSupply(); if (stakingPoolBalance > 0) { uint256 newStake = stakingPoolBalance * amount / (activeSupply + dormantStake); _mint(stakingPool, newStake); } twabController.mint(receiver, SafeCast.toUint96(amount)); emit Transfer(address(0), receiver, amount); } /** * @notice Destroys tokens from `_owner` and reduces the total supply. * @dev Emits a {Transfer} event with `to` set to the zero address. * @dev `_owner` cannot be the zero address. * @dev `_owner` must have at least `_amount` tokens. * @param _owner The owner of the tokens * @param _amount The amount of tokens to burn */ function _burnHarb(address _owner, uint256 _amount) internal { // TODO twabController.burn(_owner, SafeCast.toUint96(_amount)); emit Transfer(_owner, address(0), _amount); } /** * @notice Transfers tokens from one account to another. * @dev Emits a {Transfer} event. * @dev `_from` cannot be the zero address. * @dev `_to` cannot be the zero address. * @dev `_from` must have a balance of at least `_amount`. * @param _from Address to transfer from * @param _to Address to transfer to * @param _amount The amount of tokens to transfer */ function _transfer(address _from, address _to, uint256 _amount) internal virtual override { twabController.transfer(_from, _to, SafeCast.toUint96(_amount)); emit Transfer(_from, _to, _amount); } }