From b243874f02c3650568940b787495c292cd3f9eed Mon Sep 17 00:00:00 2001 From: JulesCrown Date: Thu, 18 Jul 2024 07:35:39 +0200 Subject: [PATCH] added overflow checks --- onchain/script/Deploy.sol | 4 +- onchain/src/{Harb.sol => Harberg.sol} | 207 +++++++++++------- onchain/src/LiquidityManager.sol | 58 +++-- onchain/src/Stake.sol | 67 +++--- onchain/test/{Harb.t.sol => Harberg.t.sol} | 238 +++++++++++++-------- onchain/test/LiquidityManager.t.sol | 148 ++++++++----- onchain/test/Stake.t.sol | 98 ++++----- 7 files changed, 493 insertions(+), 327 deletions(-) rename onchain/src/{Harb.sol => Harberg.sol} (56%) rename onchain/test/{Harb.t.sol => Harberg.t.sol} (58%) diff --git a/onchain/script/Deploy.sol b/onchain/script/Deploy.sol index acb4b59..9301f78 100644 --- a/onchain/script/Deploy.sol +++ b/onchain/script/Deploy.sol @@ -4,7 +4,7 @@ import "forge-std/Script.sol"; import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; -import "../src/Harb.sol"; +import "../src/Harberg.sol"; import "../src/Stake.sol"; import {LiquidityManager} from "../src/LiquidityManager.sol"; @@ -60,7 +60,7 @@ contract SepoliaScript is Script { TwabController tc = TwabController(TWABC); // in case you want to deploy an new TwabController //TwabController tc = new TwabController(60 * 60, uint32(block.timestamp)); - Harb harb = new Harb("Harberger Tax", "HARB", tc); + Harberg harb = new Harberg("Harbergerger Tax", "HARB", tc); token0isWeth = address(WETH) < address(harb); Stake stake = new Stake(address(harb)); harb.setStakingPool(address(stake)); diff --git a/onchain/src/Harb.sol b/onchain/src/Harberg.sol similarity index 56% rename from onchain/src/Harb.sol rename to onchain/src/Harberg.sol index c748d39..41aee28 100644 --- a/onchain/src/Harb.sol +++ b/onchain/src/Harberg.sol @@ -1,72 +1,78 @@ // 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 {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol"; import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; -import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; import {Math} from "@openzeppelin/utils/math/Math.sol"; +import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; /** - * @title Harb ERC20 Token - * @notice Implements an ERC20 token with mechanisms for minting, burning, and transferring tokens, - * integrated with a TWAB controller for time-weighted balance tracking. This token supports - * a novel economic model that finances Universal Basic Income (UBI) through a Harberger tax, - * applied to staked tokens. The liquidityManager manages token supply to stabilize market liquidity. + * @title Harberg ERC20 Token + * @notice This contract implements an ERC20 token with mechanisms for minting, burning, and transferring tokens, + * integrated with a TWAB controller for time-weighted balance tracking. This token supports a novel economic model + * that finances Universal Basic Income (UBI) through a Harberger tax applied to staked tokens. The liquidity manager + * manages token supply to stabilize market liquidity. */ -contract Harb is ERC20, ERC20Permit { +contract Harberg is ERC20, ERC20Permit { using Math for uint256; - - // only working with UNI V3 1% fee tier pools + // Total tax collected so far + uint256 public sumTaxCollected; + // Constant for UNI V3 1% fee tier pools uint24 private constant FEE = uint24(10_000); - // to prevent framentation of staking positions, a minimum size of the stake is introduced. - // snatching 600 positions will blow block gas limit, 20% of supply can be staked so 5 * 600 is a good start - uint256 MIN_STAKE_FRACTION = 3000; - // later it will keep small stakers out, so let's have positions shrink, balancing the risk of high gas cost. - uint256 MAX_STAKE_FRACTION = 100000; - // from PoolTogether: the beginning timestamp for the first period. This allows us to maximize storage as well as line up periods with a chosen timestamp. + // Minimum fraction of the total supply required for staking to prevent fragmentation of staking positions + uint256 private constant MIN_STAKE_FRACTION = 3000; + // Maximum fraction of the total supply allowed for staking to manage gas costs + uint256 private constant MAX_STAKE_FRACTION = 100000; + // Period offset for TWAB calculations uint256 private immutable PERIOD_OFFSET; - // from PoolTogether: the minimum period length for Observations. When a period elapses, a new Observation is recorded, otherwise the most recent Observation is updated. + // Minimum period length for TWAB observations uint256 private immutable PERIOD_LENGTH; - //periphery contracts + // Immutable reference to the TWAB controller TwabController private immutable twabController; + // Address of the liquidity manager address private liquidityManager; + // Address of the staking pool address private stakingPool; + // Address of the liquidity pool address private liquidityPool; + // Address of the tax pool address public constant TAX_POOL = address(2); - - /* ============ Public Variables ============ */ - uint256 public sumTaxCollected; + // Previous total supply for staking calculations uint256 public previousTotalSupply; + // Minimum fraction of the total supply required for staking uint256 public minStakeSupplyFraction; - + // Structure to hold UBI title information for each account struct UbiTitle { uint256 sumTaxCollected; uint256 time; } + // Mapping to store UBI titles for each account mapping(address => UbiTitle) public ubiTitles; - /* ============ Errors ============ */ + // Custom errors error ZeroAddressInConstructor(); error ZeroAddressInSetter(); error AddressAlreadySet(); + // Event emitted when UBI is claimed event UbiClaimed(address indexed owner, uint256 ubiAmount); - /// @dev Function modifier to ensure that the caller is the liquidityManager + // Modifier to restrict access to the liquidity manager modifier onlyLiquidityManager() { - require(msg.sender == address(liquidityManager), "Harb/only-lm"); + require(msg.sender == address(liquidityManager), "Harberg/only-lm"); _; } /** - * @notice TwabERC20 Constructor + * @notice Constructor for the Harberg token * @param name_ The name of the token - * @param symbol_ The token symbol + * @param symbol_ The symbol of the token + * @param twabController_ The TWAB controller contract */ constructor(string memory name_, string memory symbol_, TwabController twabController_) ERC20(name_, symbol_) @@ -79,48 +85,64 @@ contract Harb is ERC20, ERC20Permit { minStakeSupplyFraction = MIN_STAKE_FRACTION; } - /// @notice Sets the address for the liquidityPool. Used once post-deployment to initialize the contract. - /// @dev Should be called only once right after the contract deployment to set the liquidity pool address. - /// Throws AddressAlreadySet if called more than once. - /// @param liquidityPool_ The address of the liquidity pool. + /** + * @notice Sets the address for the liquidityPool. Used once post-deployment to initialize the contract. + * @dev Should be called only once right after the contract deployment to set the liquidity pool address. + * Throws AddressAlreadySet if called more than once. + * @param liquidityPool_ The address of the liquidity pool. + */ function setLiquidityPool(address liquidityPool_) external { if (address(0) == liquidityPool_) revert ZeroAddressInSetter(); if (liquidityPool != address(0)) revert AddressAlreadySet(); liquidityPool = liquidityPool_; } - /// @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. + /** + * @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. + /** + * @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_; } - function getPeripheryContracts() external view returns (address, address, address, address) { + /** + * @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, address, address) { return (address(twabController), liquidityManager, stakingPool, liquidityPool); } + /** + * @notice Calculates the minimum stake based on the previous total supply + * @return The minimum stake amount + */ function minStake() external view returns (uint256) { return previousTotalSupply / minStakeSupplyFraction; } - /// @notice Allows the liquidityManager 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. + /** + * @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 { _mint(address(liquidityManager), _amount); if (previousTotalSupply == 0) { @@ -128,18 +150,28 @@ contract Harb is ERC20, ERC20Permit { } } - /// @notice Allows the liquidityManager 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. + /** + * @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 { _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 Sets the minimum stake supply fraction + * @param _mssf The minimum stake supply fraction value + */ function setMinStakeSupplyFraction(uint256 _mssf) external onlyLiquidityManager { require(_mssf >= MIN_STAKE_FRACTION, "minStakeSupplyFraction below allowed min"); require(_mssf <= MAX_STAKE_FRACTION, "minStakeSupplyFraction above allow max"); @@ -148,16 +180,24 @@ contract Harb is ERC20, ERC20Permit { /* ============ Public ERC20 Overrides ============ */ - /// @inheritdoc ERC20 + /** + * @inheritdoc ERC20 + */ function balanceOf(address _account) public view override(ERC20) returns (uint256) { return twabController.balanceOf(address(this), _account); } - /// @inheritdoc ERC20 + /** + * @inheritdoc ERC20 + */ function totalSupply() public view override(ERC20) returns (uint256) { return twabController.totalSupply(address(this)); } + /** + * @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(liquidityPool) - balanceOf(liquidityManager); } @@ -191,24 +231,23 @@ contract Harb is ERC20, ERC20Permit { } /** - * @notice Destroys tokens from `_owner` and reduces the total supply. + * @notice Burns tokens from `_owner` and decreases 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 + * @param owner Address that will have tokens burnt + * @param amount Tokens to burn */ - function _burn(address _owner, uint256 _amount) internal override { - if (_amount > 0) { + function _burn(address owner, uint256 amount) internal override { + if (amount > 0) { // shrink staking pool proportional to economy uint256 stakingPoolBalance = balanceOf(stakingPool); if (stakingPoolBalance > 0) { - uint256 excessStake = stakingPoolBalance * _amount / (totalSupply() - stakingPoolBalance); + uint256 excessStake = stakingPoolBalance * amount / (totalSupply() - stakingPoolBalance); twabController.burn(stakingPool, SafeCast.toUint96(excessStake)); emit Transfer(stakingPool, address(0), excessStake); } - twabController.burn(_owner, SafeCast.toUint96(_amount)); - emit Transfer(_owner, address(0), _amount); + twabController.burn(owner, SafeCast.toUint96(amount)); + emit Transfer(owner, address(0), amount); } } @@ -224,7 +263,9 @@ contract Harb is ERC20, ERC20Permit { */ function _transfer(address _from, address _to, uint256 _amount) internal override { if (_to == TAX_POOL) { - sumTaxCollected += _amount; + unchecked { + sumTaxCollected += _amount; + } } else if (ubiTitles[_to].time == 0 && _amount > 0) { // new account, start UBI title ubiTitles[_to].sumTaxCollected = sumTaxCollected; @@ -236,8 +277,16 @@ contract Harb is ERC20, ERC20Permit { /* ============ UBI stuff ============ */ + /** + * @notice Calculates the UBI due to an account based on time-weighted average balances. + * @dev Uses historic TWAB data to determine an account's proportionate share of collected taxes since last claim. + * @param _account The account whose UBI is being calculated. + * @param lastTaxClaimed The timestamp of the last UBI claim. + * @param _sumTaxCollected The tax collected up to the last claim. + * @return amountDue The amount of UBI due to the account. + * @return lastPeriodEndAt The timestamp marking the end of the last period considered for UBI calculation. + */ function ubiDue(address _account, uint256 lastTaxClaimed, uint256 _sumTaxCollected) internal view returns (uint256 amountDue, uint256 lastPeriodEndAt) { - lastPeriodEndAt = ((block.timestamp - PERIOD_OFFSET) / uint256(PERIOD_LENGTH)) * PERIOD_LENGTH + PERIOD_OFFSET - 1; if (lastTaxClaimed == 0 || lastTaxClaimed > lastPeriodEndAt || lastPeriodEndAt - lastTaxClaimed < PERIOD_LENGTH) { return (0, lastPeriodEndAt); @@ -248,30 +297,43 @@ contract Harb is ERC20, ERC20Permit { uint256 taxTwab = twabController.getTwabBetween(address(this), TAX_POOL, lastTaxClaimed, lastPeriodEndAt); uint256 totalSupplyTwab = twabController.getTotalSupplyTwabBetween(address(this), lastTaxClaimed, lastPeriodEndAt); - uint256 taxCollectedSinceLastClaim = sumTaxCollected - _sumTaxCollected; + //uint256 taxCollectedSinceLastClaim = sumTaxCollected - _sumTaxCollected; + uint256 taxCollectedSinceLastClaim; + if (sumTaxCollected >= _sumTaxCollected) { + taxCollectedSinceLastClaim = sumTaxCollected - _sumTaxCollected; + } else { + // Handle the wrap-around case + taxCollectedSinceLastClaim = type(uint256).max - _sumTaxCollected + sumTaxCollected + 1; + } amountDue = taxCollectedSinceLastClaim.mulDiv(accountTwab, (totalSupplyTwab - stakeTwab - poolTwab - taxTwab), Math.Rounding.Down); } - /// @notice Calculates the UBI due to an account based on time-weighted average balances. - /// @dev Uses historic TWAB data to determine an account's proportionate share of collected taxes since last claim. - /// `lastTaxClaimed` is the timestamp of the last UBI claim. `_sumTaxCollected` is the tax collected up to the last claim. - /// @param _account The account whose UBI is being calculated. - /// @return amountDue The amount of UBI due to the account. - /// @return lastPeriodEndAt The timestamp marking the end of the last period considered for UBI calculation. + /** + * @notice Calculates the UBI due to an account based on time-weighted average balances. + * @dev Uses historic TWAB data to determine an account's proportionate share of collected taxes since last claim. + * @param _account The account whose UBI is being calculated. + * @return amountDue The amount of UBI due to the account. + * @return lastPeriodEndAt The timestamp marking the end of the last period considered for UBI calculation. + */ function getUbiDue(address _account) public view returns (uint256 amountDue, uint256 lastPeriodEndAt) { UbiTitle storage lastUbiTitle = ubiTitles[_account]; return ubiDue(_account, lastUbiTitle.time, lastUbiTitle.sumTaxCollected); } - /// @notice Claims the calculated UBI amount for the caller. - /// @dev Transfers the due UBI from the tax pool to the account, updating the UBI title. - /// Emits UbiClaimed event on successful transfer. + /** + * @notice Claims the calculated UBI amount for the caller. + * @dev Transfers the due UBI from the tax pool to the account, updating the UBI title. + * Emits UbiClaimed event on successful transfer. + * @param _account The account claiming the UBI. + * @return ubiAmountDue The amount of UBI claimed. + */ function claimUbi(address _account) external returns (uint256 ubiAmountDue) { UbiTitle storage lastUbiTitle = ubiTitles[_account]; uint256 lastPeriodEndAt; (ubiAmountDue, lastPeriodEndAt) = ubiDue(_account, lastUbiTitle.time, lastUbiTitle.sumTaxCollected); if (ubiAmountDue > 0) { + ubiTitles[_account].sumTaxCollected = sumTaxCollected; ubiTitles[_account].time = lastPeriodEndAt; twabController.transfer(TAX_POOL, _account, SafeCast.toUint96(ubiAmountDue)); @@ -280,5 +342,4 @@ contract Harb is ERC20, ERC20Permit { revert("No UBI to claim."); } } - } diff --git a/onchain/src/LiquidityManager.sol b/onchain/src/LiquidityManager.sol index 95f091b..0085a6f 100644 --- a/onchain/src/LiquidityManager.sol +++ b/onchain/src/LiquidityManager.sol @@ -12,20 +12,23 @@ import "@openzeppelin/token/ERC20/IERC20.sol"; import "@openzeppelin/utils/math/SignedMath.sol"; import {ABDKMath64x64} from "@abdk/ABDKMath64x64.sol"; import "./interfaces/IWETH9.sol"; -import {Harb} from "./Harb.sol"; +import {Harberg} from "./Harberg.sol"; /** - * @title LiquidityManager for Harb Token on Uniswap V3 - * @notice Manages liquidity provisioning on Uniswap V3 for the Harb token by maintaining three distinct positions: - * - Floor Position: Ensures a minimum price support by having enough reserve assets to potentially buy back the circulating supply of Harb. + * @title LiquidityManager for Harberg Token on Uniswap V3 + * @notice Manages liquidity provisioning on Uniswap V3 for the Harberg token by maintaining three distinct positions: + * - Floor Position: Ensures a minimum price support by having enough reserve assets to potentially buy back the circulating supply of Harberg. * - Anchor Position: Provides liquidity around the current market price to facilitate trading and maintain market stability. - * - Discovery Position: Expands liquidity by minting new Harb tokens as the price rises, capturing potential growth in the ecosystem. - * The contract dynamically adjusts these positions in response to market movements to maintain strategic liquidity levels and support the Harb token's price. + * - Discovery Position: Expands liquidity by minting new Harberg tokens as the price rises, capturing potential growth in the ecosystem. + * The contract dynamically adjusts these positions in response to market movements to maintain strategic liquidity levels and support the Harberg token's price. * It also collects and transfers fees generated from trading activities to a designated fee destination. * @dev Utilizes Uniswap V3's concentrated liquidity feature, enabling highly efficient use of capital. */ contract LiquidityManager { + // State variables to track total ETH spent + uint256 public cumulativeVolumeWeightedPrice; + uint256 public cumulativeVolume; // the minimum granularity of liquidity positions in the Uniswap V3 pool. this is a 1% pool. int24 internal constant TICK_SPACING = 200; // defines the width of the anchor position from the current price to discovery position. @@ -52,7 +55,7 @@ contract LiquidityManager { // the address of the Uniswap V3 factory address private immutable factory; IWETH9 private immutable weth; - Harb private immutable harb; + Harberg private immutable harb; IUniswapV3Pool private immutable pool; bool private immutable token0isWeth; PoolKey private poolKey; @@ -68,9 +71,6 @@ contract LiquidityManager { } mapping(Stage => TokenPosition) public positions; - // State variables to track total ETH spent - uint256 public cumulativeVolumeWeightedPrice; - uint256 public cumulativeVolume; // the address where liquidity fees will be sent address public feeDestination; // the minimum share of ETH that will be put into the anchor @@ -90,17 +90,17 @@ contract LiquidityManager { _; } - /// @notice Creates a liquidity manager for managing Harb token liquidity on Uniswap V3. + /// @notice Creates a liquidity manager for managing Harberg token liquidity on Uniswap V3. /// @param _factory The address of the Uniswap V3 factory. /// @param _WETH9 The address of the WETH contract for handling ETH in trades. - /// @param _harb The address of the Harb token contract. - /// @dev Computes the Uniswap pool address for the Harb-WETH pair and sets up the initial configuration for the liquidity manager. + /// @param _harb The address of the Harberg token contract. + /// @dev Computes the Uniswap pool address for the Harberg-WETH pair and sets up the initial configuration for the liquidity manager. constructor(address _factory, address _WETH9, address _harb) { factory = _factory; weth = IWETH9(_WETH9); poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE); pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey)); - harb = Harb(_harb); + harb = Harberg(_harb); token0isWeth = _WETH9 < _harb; anchorLiquidityShare = MAX_ANCHOR_LIQ_SHARE; capitalInfefficiency = MIN_CAPITAL_INEFFICIENCY; @@ -109,7 +109,7 @@ contract LiquidityManager { /// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens. /// @param amount0Owed The amount of token0 owed for the liquidity provision. /// @param amount1Owed The amount of token1 owed for the liquidity provision. - /// @dev This function mints Harb tokens as needed and handles WETH deposits for ETH conversions during liquidity interactions. + /// @dev This function mints Harberg tokens as needed and handles WETH deposits for ETH conversions during liquidity interactions. function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external { CallbackValidation.verifyCallback(factory, poolKey); // take care of harb @@ -152,9 +152,9 @@ contract LiquidityManager { receive() external payable { } - /// @notice Calculates the Uniswap V3 tick corresponding to a given price ratio between Harb and ETH. + /// @notice Calculates the Uniswap V3 tick corresponding to a given price ratio between Harberg and ETH. /// @param t0isWeth Boolean flag indicating if token0 is WETH. - /// @param tokenAmount Amount of the Harb token. + /// @param tokenAmount Amount of the Harberg token. /// @param ethAmount Amount of Ethereum. /// @return tick_ The calculated tick for the given price ratio. function tickAtPrice(bool t0isWeth, uint256 tokenAmount, uint256 ethAmount) internal pure returns (int24 tick_) { @@ -363,8 +363,15 @@ contract LiquidityManager { IERC20(address(weth)).transfer(feeDestination, fee0); uint256 volume = fee0 * 100; uint256 volumeWeightedPrice = currentPrice * volume; - cumulativeVolumeWeightedPrice += volumeWeightedPrice; - cumulativeVolume += volume; + // Check for potential overflow + if (cumulativeVolumeWeightedPrice > type(uint256).max - volumeWeightedPrice) { + // Handle overflow: reset + cumulativeVolumeWeightedPrice = volumeWeightedPrice; + cumulativeVolume = volume; + } else { + cumulativeVolumeWeightedPrice += volumeWeightedPrice; + cumulativeVolume += volume; + } } else { IERC20(address(harb)).transfer(feeDestination, fee0); } @@ -376,8 +383,15 @@ contract LiquidityManager { IERC20(address(weth)).transfer(feeDestination, fee1); uint256 volume = fee1 * 100; uint256 volumeWeightedPrice = currentPrice * volume; - cumulativeVolumeWeightedPrice += volumeWeightedPrice; - cumulativeVolume += volume; + // Check for potential overflow + if (cumulativeVolumeWeightedPrice > type(uint256).max - volumeWeightedPrice) { + // Handle overflow: reset + cumulativeVolumeWeightedPrice = volumeWeightedPrice; + cumulativeVolume = volume; + } else { + cumulativeVolumeWeightedPrice += volumeWeightedPrice; + cumulativeVolume += volume; + } } } } @@ -401,7 +415,7 @@ contract LiquidityManager { return (currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION); } - /// @notice Adjusts liquidity positions in response to an increase or decrease in the Harb token's price. + /// @notice Adjusts liquidity positions in response to an increase or decrease in the Harberg token's price. /// @dev This function should be called when significant price movement is detected. It recalibrates the liquidity ranges to align with the new market conditions. function recenter() external { // Fetch the current tick from the Uniswap V3 pool diff --git a/onchain/src/Stake.sol b/onchain/src/Stake.sol index 5471c24..2414b93 100644 --- a/onchain/src/Stake.sol +++ b/onchain/src/Stake.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.19; import {IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; -import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol"; -import "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol"; +import {IERC20Metadata} from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol"; +import {ERC20Permit} from"@openzeppelin/token/ERC20/extensions/ERC20Permit.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import {Math} from "@openzeppelin/utils/math/Math.sol"; -import "./Harb.sol"; +import {Harberg} from "./Harberg.sol"; error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake); error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare); /** - * @title Stake Contract for Harb Token - * @notice This contract manages the staking positions for the Harb token, allowing users to stake tokens + * @title Stake Contract for Harberg Token + * @notice This contract manages the staking positions for the Harberg token, allowing users to stake tokens * in exchange for a share of the total supply. Stakers can set and adjust tax rates on their stakes, * which affect the Universal Basic Income (UBI) paid from the tax pool. * @@ -49,11 +49,11 @@ contract Stake { error NoPermission(address requester, address owner); error PositionNotFound(uint256 positionId, address requester); - event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 harbDeposit, uint256 share, uint32 taxRate); + event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 harbergDeposit, uint256 share, uint32 taxRate); event PositionTaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxPaid, uint256 newShares, uint256 taxRate); event PositionRateHiked(uint256 indexed positionId, address indexed owner, uint256 newTaxRate); - event PositionShrunk(uint256 indexed positionId, address indexed owner, uint256 newShares, uint256 harbPayout); - event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 harbPayout); + event PositionShrunk(uint256 indexed positionId, address indexed owner, uint256 newShares, uint256 harbergPayout); + event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 harbergPayout); struct StakingPosition { uint256 share; @@ -63,7 +63,7 @@ contract Stake { uint32 taxRate; // e.g. value of 60 = 60% tax per year } - Harb private immutable harb; + Harberg private immutable harberg; address private immutable taxPool; uint256 public immutable totalSupply; @@ -72,13 +72,13 @@ contract Stake { mapping(uint256 => StakingPosition) public positions; - /// @notice Initializes the stake contract with references to the Harb contract and sets the initial position ID. - /// @param _harb Address of the Harb contract which this Stake contract interacts with. - /// @dev Sets up the total supply based on the decimals of the Harb token plus a fixed offset. - constructor(address _harb) { - harb = Harb(_harb); - totalSupply = 10 ** (harb.decimals() + DECIMAL_OFFSET); - taxPool = Harb(_harb).TAX_POOL(); + /// @notice Initializes the stake contract with references to the Harberg contract and sets the initial position ID. + /// @param _harberg Address of the Harberg contract which this Stake contract interacts with. + /// @dev Sets up the total supply based on the decimals of the Harberg token plus a fixed offset. + constructor(address _harberg) { + harberg = Harberg(_harberg); + totalSupply = 10 ** (harberg.decimals() + DECIMAL_OFFSET); + taxPool = Harberg(_harberg).TAX_POOL(); // start counting somewhere nextPositionId = 654321; } @@ -100,8 +100,6 @@ contract Stake { // can not pay more tax than value of position taxAmountDue = assetsBefore; } - SafeERC20.safeTransfer(harb, taxPool, taxAmountDue); - if (assetsBefore - taxAmountDue > 0) { // if something left over, update storage uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue); @@ -118,9 +116,10 @@ contract Stake { delete pos.creationTime; delete pos.share; } + SafeERC20.safeTransfer(harberg, taxPool, taxAmountDue); } - /// @dev Internal function to close a staking position, transferring the remaining Harb tokens back to the owner after tax payment. + /// @dev Internal function to close a staking position, transferring the remaining Harberg tokens back to the owner after tax payment. function _exitPosition(uint256 positionId, StakingPosition storage pos) private { outstandingStake -= pos.share; address owner = pos.owner; @@ -129,35 +128,35 @@ contract Stake { delete pos.owner; delete pos.creationTime; delete pos.share; - SafeERC20.safeTransfer(harb, owner, assets); + SafeERC20.safeTransfer(harberg, owner, assets); } - /// @dev Internal function to reduce the size of a staking position by a specified number of shares, transferring the corresponding Harb tokens to the owner. + /// @dev Internal function to reduce the size of a staking position by a specified number of shares, transferring the corresponding Harberg tokens to the owner. function _shrinkPosition(uint256 positionId, StakingPosition storage pos, uint256 sharesToTake) private { require (sharesToTake < pos.share, "position too small"); uint256 assets = sharesToAssets(sharesToTake); pos.share -= sharesToTake; outstandingStake -= sharesToTake; emit PositionShrunk(positionId, pos.owner, pos.share, assets); - SafeERC20.safeTransfer(harb, pos.owner, assets); + SafeERC20.safeTransfer(harberg, pos.owner, assets); } - /// @notice Converts Harb token assets to shares of the total staking pool. - /// @param assets Number of Harb tokens to convert. - /// @return Number of shares corresponding to the input assets based on the current total supply of Harb tokens. + /// @notice Converts Harberg token assets to shares of the total staking pool. + /// @param assets Number of Harberg tokens to convert. + /// @return Number of shares corresponding to the input assets based on the current total supply of Harberg tokens. function assetsToShares(uint256 assets) public view returns (uint256) { - return assets.mulDiv(totalSupply, harb.totalSupply(), Math.Rounding.Down); + return assets.mulDiv(totalSupply, harberg.totalSupply(), Math.Rounding.Down); } - /// @notice Converts shares of the total staking pool back to Harb token assets. + /// @notice Converts shares of the total staking pool back to Harberg token assets. /// @param shares Number of shares to convert. - /// @return The equivalent number of Harb tokens for the given shares. + /// @return The equivalent number of Harberg tokens for the given shares. function sharesToAssets(uint256 shares) public view returns (uint256) { - return shares.mulDiv(harb.totalSupply(), totalSupply, Math.Rounding.Down); + return shares.mulDiv(harberg.totalSupply(), totalSupply, Math.Rounding.Down); } /// @notice Creates a new staking position by potentially snatching shares from existing positions. - /// @param assets Amount of Harb tokens to convert into a staking position. + /// @param assets Amount of Harberg tokens to convert into a staking position. /// @param receiver Address that will own the new staking position. /// @param taxRate The initial tax rate for the new staking position. /// @param positionsToSnatch Array of position IDs that the new position will replace by snatching. @@ -172,7 +171,7 @@ contract Stake { { // check that position size is at least minStake // to prevent excessive fragmentation, increasing snatch cost - uint256 minStake = harb.minStake(); + uint256 minStake = harberg.minStake(); if (assets < minStake) { revert StakeTooLow(receiver, assets, minStake); } @@ -241,7 +240,7 @@ contract Stake { } // transfer - SafeERC20.safeTransferFrom(harb, msg.sender, address(this), assets); + SafeERC20.safeTransferFrom(harberg, msg.sender, address(this), assets); // mint positionId = nextPositionId++; @@ -257,7 +256,7 @@ contract Stake { } /// @notice Combines an ERC20 permit operation with the snatch function, allowing a staking position creation in one transaction. - /// @param assets Number of Harb tokens to stake. + /// @param assets Number of Harberg tokens to stake. /// @param receiver Address that will own the new staking position. /// @param taxRate The initial tax rate for the new staking position. /// @param positionsToSnatch Array of position IDs that the new position will replace by snatching. @@ -279,7 +278,7 @@ contract Stake { ) external returns (uint256 positionId) { - ERC20Permit(address(harb)).permit(receiver, address(this), assets, deadline, v, r, s); + ERC20Permit(address(harberg)).permit(receiver, address(this), assets, deadline, v, r, s); return snatch(assets, receiver, taxRate, positionsToSnatch); } diff --git a/onchain/test/Harb.t.sol b/onchain/test/Harberg.t.sol similarity index 58% rename from onchain/test/Harb.t.sol rename to onchain/test/Harberg.t.sol index 738e267..ada211d 100644 --- a/onchain/test/Harb.t.sol +++ b/onchain/test/Harberg.t.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "forge-std/console.sol"; import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; -import "../src/Harb.sol"; +import "../src/Harberg.sol"; -contract HarbTest is Test { +contract HarbergTest is Test { TwabController tc; - Harb harb; + Harberg harberg; address stakingPool; address liquidityPool; address liquidityManager; @@ -16,37 +16,37 @@ contract HarbTest is Test { function setUp() public { tc = new TwabController(60 * 60, uint32(block.timestamp)); - harb = new Harb("HARB", "HARB", tc); - taxPool = harb.TAX_POOL(); + harberg = new Harberg("HARB", "HARB", tc); + taxPool = harberg.TAX_POOL(); stakingPool = makeAddr("stakingPool"); - harb.setStakingPool(stakingPool); + harberg.setStakingPool(stakingPool); liquidityPool = makeAddr("liquidityPool"); - harb.setLiquidityPool(liquidityPool); + harberg.setLiquidityPool(liquidityPool); liquidityManager = makeAddr("liquidityManager"); - harb.setLiquidityManager(liquidityManager); + harberg.setLiquidityManager(liquidityManager); } // Simulates staking by transferring tokens to the stakingPool address. function simulateStake(uint256 amount) internal { // the amount of token has to be available on the balance // of the test contract - harb.transfer(stakingPool, amount); + harberg.transfer(stakingPool, amount); } // Simulates unstaking by transferring tokens from the stakingPool back to a given address. function simulateUnstake(uint256 amount) internal { // Direct transfer from the stakingPool to 'to' address to simulate unstaking vm.prank(stakingPool); // Assuming 'stake' contract would allow this in an actual scenario - harb.transfer(address(this), amount); + harberg.transfer(address(this), amount); } - function testHarbConstructor() public view { + function testHarbergConstructor() public view { // Check if the token details are set as expected - assertEq(harb.name(), "HARB"); - assertEq(harb.symbol(), "HARB"); + assertEq(harberg.name(), "HARB"); + assertEq(harberg.symbol(), "HARB"); // Confirm that the TwabController address is correctly set - (address _tc, address _lm, address _sp, address _lp) = harb.getPeripheryContracts(); + (address _tc, address _lm, address _sp, address _lp) = harberg.peripheryContracts(); assertEq(_tc, address(tc)); assertEq(_lm, liquidityManager); assertEq(_sp, stakingPool); @@ -54,67 +54,67 @@ contract HarbTest is Test { } function testMintWithEmptyStakingPool() public { - uint256 initialSupply = harb.totalSupply(); + uint256 initialSupply = harberg.totalSupply(); uint256 mintAmount = 1000 * 1e18; // 1000 HARB tokens vm.prank(address(liquidityManager)); - harb.mint(mintAmount); + harberg.mint(mintAmount); // Check if the total supply has increased correctly - assertEq(harb.totalSupply(), initialSupply + mintAmount); + assertEq(harberg.totalSupply(), initialSupply + mintAmount); // Check if the staking pool balance is still 0, as before - assertEq(harb.balanceOf(stakingPool), 0); + assertEq(harberg.balanceOf(stakingPool), 0); } function testBurnWithEmptyStakingPool() public { - uint256 initialSupply = harb.totalSupply(); + uint256 initialSupply = harberg.totalSupply(); uint256 burnAmount = 500 * 1e18; // 500 HARB tokens // First, mint some tokens to burn vm.prank(address(liquidityManager)); - harb.mint(burnAmount); + harberg.mint(burnAmount); vm.prank(address(liquidityManager)); - harb.burn(burnAmount); + harberg.burn(burnAmount); // Check if the total supply has decreased correctly - assertEq(harb.totalSupply(), initialSupply); + assertEq(harberg.totalSupply(), initialSupply); // Check if the staking pool balance has decreased correctly - assertEq(harb.balanceOf(stakingPool), 0); + assertEq(harberg.balanceOf(stakingPool), 0); } function testMintImpactOnSimulatedStaking() public { - uint256 initialStakingPoolBalance = harb.balanceOf(stakingPool); + uint256 initialStakingPoolBalance = harberg.balanceOf(stakingPool); uint256 mintAmount = 1000 * 1e18; // 1000 HARB tokens // Ensure the test contract has enough tokens to simulate staking vm.prank(address(liquidityManager)); - harb.mint(mintAmount); + harberg.mint(mintAmount); vm.prank(address(liquidityManager)); - harb.transfer(address(this), mintAmount); + harberg.transfer(address(this), mintAmount); // Simulate staking of the minted amount simulateStake(mintAmount); // Check balances after simulated staking - assertEq(harb.balanceOf(stakingPool), initialStakingPoolBalance + mintAmount); + assertEq(harberg.balanceOf(stakingPool), initialStakingPoolBalance + mintAmount); } function testUnstakeImpactOnTotalSupply() public { uint256 stakeAmount = 500 * 1e18; // 500 HARB tokens // Ensure the test contract has enough tokens to simulate staking vm.prank(address(liquidityManager)); - harb.mint(stakeAmount); + harberg.mint(stakeAmount); vm.prank(address(liquidityManager)); - harb.transfer(address(this), stakeAmount); + harberg.transfer(address(this), stakeAmount); - uint256 initialTotalSupply = harb.totalSupply(); + uint256 initialTotalSupply = harberg.totalSupply(); // Simulate staking and then unstaking simulateStake(stakeAmount); simulateUnstake(stakeAmount); // Check total supply remains unchanged after unstake - assertEq(harb.totalSupply(), initialTotalSupply); + assertEq(harberg.totalSupply(), initialTotalSupply); } // Fuzz test for mint function with varying stake amounts @@ -122,29 +122,29 @@ contract HarbTest is Test { uint256 initialAmount = 500 * 1e18; // Ensure the test contract has enough tokens to simulate staking vm.prank(address(liquidityManager)); - harb.mint(initialAmount); + harberg.mint(initialAmount); vm.prank(address(liquidityManager)); - harb.transfer(address(this), initialAmount); + harberg.transfer(address(this), initialAmount); // Limit fuzzing input to 0% - 20% uint8 effectiveStakePercentage = _stakePercentage % 21; uint256 stakeAmount = (initialAmount * effectiveStakePercentage) / 100; simulateStake(stakeAmount); - uint256 initialTotalSupply = harb.totalSupply(); - uint256 initialStakingPoolBalance = harb.balanceOf(stakingPool); + uint256 initialTotalSupply = harberg.totalSupply(); + uint256 initialStakingPoolBalance = harberg.balanceOf(stakingPool); mintAmount = bound(mintAmount, 0, 500 * 1e18); uint256 expectedNewStake = initialStakingPoolBalance * mintAmount / (initialTotalSupply - initialStakingPoolBalance); vm.prank(address(liquidityManager)); - harb.mint(mintAmount); + harberg.mint(mintAmount); uint256 expectedStakingPoolBalance = initialStakingPoolBalance + expectedNewStake; uint256 expectedTotalSupply = initialTotalSupply + mintAmount + expectedNewStake; - assertEq(harb.totalSupply(), expectedTotalSupply, "Total supply did not match expected after mint."); - assertEq(harb.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after mint."); + assertEq(harberg.totalSupply(), expectedTotalSupply, "Total supply did not match expected after mint."); + assertEq(harberg.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after mint."); } // Fuzz test for burn function with varying stake amounts @@ -152,45 +152,45 @@ contract HarbTest is Test { uint256 mintAmount = 500 * 1e18; // Ensure the test contract has enough tokens to simulate staking vm.prank(address(liquidityManager)); - harb.mint(mintAmount); + harberg.mint(mintAmount); // Limit fuzzing input to 0% - 20% uint8 effectiveStakePercentage = _stakePercentage % 21; uint256 stakeAmount = (mintAmount * effectiveStakePercentage) / 100; vm.prank(address(liquidityManager)); - harb.transfer(address(this), stakeAmount); + harberg.transfer(address(this), stakeAmount); simulateStake(stakeAmount); burnAmount = bound(burnAmount, 0, 200 * 1e18); - uint256 initialTotalSupply = harb.totalSupply(); - uint256 initialStakingPoolBalance = harb.balanceOf(stakingPool); + uint256 initialTotalSupply = harberg.totalSupply(); + uint256 initialStakingPoolBalance = harberg.balanceOf(stakingPool); uint256 expectedExcessStake = initialStakingPoolBalance * burnAmount / (initialTotalSupply - initialStakingPoolBalance); vm.prank(address(liquidityManager)); - harb.burn(burnAmount); + harberg.burn(burnAmount); uint256 expectedStakingPoolBalance = initialStakingPoolBalance - expectedExcessStake; uint256 expectedTotalSupply = initialTotalSupply - burnAmount - expectedExcessStake; - assertEq(harb.totalSupply(), expectedTotalSupply, "Total supply did not match expected after burn."); - assertEq(harb.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after burn."); + assertEq(harberg.totalSupply(), expectedTotalSupply, "Total supply did not match expected after burn."); + assertEq(harberg.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after burn."); } function testTaxAccumulation() public { uint256 taxAmount = 100 * 1e18; // 100 HARB tokens vm.prank(address(liquidityManager)); - harb.mint(taxAmount); + harberg.mint(taxAmount); // Initial tax collected should be zero - assertEq(harb.sumTaxCollected(), 0, "Initial tax collected should be zero."); + assertEq(harberg.sumTaxCollected(), 0, "Initial tax collected should be zero."); // Simulate sending tokens to the taxPool vm.prank(address(liquidityManager)); - harb.transfer(taxPool, taxAmount); + harberg.transfer(taxPool, taxAmount); // Check that sumTaxCollected has been updated correctly - assertEq(harb.sumTaxCollected(), taxAmount, "Tax collected not updated correctly after transfer to taxPool."); + assertEq(harberg.sumTaxCollected(), taxAmount, "Tax collected not updated correctly after transfer to taxPool."); } function testUBIClaimBySingleAccountOverTime() public { @@ -200,36 +200,86 @@ contract HarbTest is Test { // Setup initial supply and distribute to user vm.prank(address(liquidityManager)); - harb.mint(initialSupply + taxAmount); + harberg.mint(initialSupply + taxAmount); vm.prank(address(liquidityManager)); - harb.transfer(user, initialSupply); + harberg.transfer(user, initialSupply); // Simulate tax collection vm.prank(address(liquidityManager)); - harb.transfer(taxPool, taxAmount); + harberg.transfer(taxPool, taxAmount); // Simulate time passage to ensure TWAB is recorded over time vm.warp(block.timestamp + 30 days); // Assert initial user balance and sumTaxCollected before claiming UBI - assertEq(harb.balanceOf(user), initialSupply, "User should hold the entire initial supply."); - assertEq(harb.sumTaxCollected(), taxAmount, "Tax collected should match the tax amount transferred."); + assertEq(harberg.balanceOf(user), initialSupply, "User should hold the entire initial supply."); + assertEq(harberg.sumTaxCollected(), taxAmount, "Tax collected should match the tax amount transferred."); // User claims UBI vm.prank(user); - harb.claimUbi(user); + harberg.claimUbi(user); // Compute expected UBI // Assume the user is the only one holding tokens, they get all the collected taxes. uint256 expectedUbiAmount = taxAmount; // Verify UBI claim - uint256 postClaimBalance = harb.balanceOf(user); + uint256 postClaimBalance = harberg.balanceOf(user); assertEq(postClaimBalance, initialSupply + expectedUbiAmount, "User's balance after claiming UBI is incorrect."); // Ensure that claiming doesn't affect the total supply uint256 expectedTotalSupply = initialSupply + taxAmount; // Include minted tax - assertEq(harb.totalSupply(), expectedTotalSupply, "Total supply should be unchanged after UBI claim."); + assertEq(harberg.totalSupply(), expectedTotalSupply, "Total supply should be unchanged after UBI claim."); + } + + function testUBIClaimBySingleAccountWithWraparound() public { + uint256 initialSupply = 1000 * 1e18; // 1000 HARB tokens + uint256 taxAmount = 200 * 1e18; // 200 HARB tokens to be collected as tax + uint256 nearMaxUint = type(uint256).max - 10; + address user = makeAddr("alice"); + + // Set sumTaxCollected to near max value to simulate wrap-around + vm.store( + address(harberg), + bytes32(uint256(9)), + bytes32(nearMaxUint) + ); + + // Read the value back to confirm it's set correctly + assertEq(harberg.sumTaxCollected(), nearMaxUint, "Initial sumTaxCollected should be near max uint256"); + + // Setup initial supply and distribute to user + vm.prank(address(liquidityManager)); + harberg.mint(initialSupply + taxAmount); + vm.prank(address(liquidityManager)); + harberg.transfer(user, initialSupply); + + // Simulate tax collection to cause overflow + vm.prank(address(liquidityManager)); + harberg.transfer(taxPool, taxAmount); + + // Simulate time passage to ensure TWAB is recorded over time + vm.warp(block.timestamp + 30 days); + + // Verify the new value of sumTaxCollected after overflow + uint256 newSumTaxCollected = harberg.sumTaxCollected(); + assertGt(taxAmount, newSumTaxCollected, "sumTaxCollected should have wrapped around and be less than taxAmount"); + + // User claims UBI + vm.prank(user); + harberg.claimUbi(user); + + // Compute expected UBI + // Assume the user is the only one holding tokens, they get all the collected taxes. + uint256 expectedUbiAmount = taxAmount; + + // Verify UBI claim + uint256 postClaimBalance = harberg.balanceOf(user); + assertApproxEqRel(postClaimBalance, initialSupply + expectedUbiAmount, 1 * 1e14, "User's balance after claiming UBI is incorrect."); + + // Ensure that claiming doesn't affect the total supply + uint256 expectedTotalSupply = initialSupply + taxAmount; // Include minted tax + assertEq(harberg.totalSupply(), expectedTotalSupply, "Total supply should be unchanged after UBI claim."); } function testUBIClaimByMultipleAccountsWithDifferentHoldingPeriods() public { @@ -241,10 +291,10 @@ contract HarbTest is Test { // Setup initial supply and distribute to users vm.startPrank(address(liquidityManager)); - harb.mint(initialSupply); - harb.transfer(account1, 400 * 1e18); // Account 1 gets 400 tokens - harb.transfer(account2, 300 * 1e18); // Account 2 gets 300 tokens - harb.transfer(account3, 300 * 1e18); // Account 3 gets 300 tokens + harberg.mint(initialSupply); + harberg.transfer(account1, 400 * 1e18); // Account 1 gets 400 tokens + harberg.transfer(account2, 300 * 1e18); // Account 2 gets 300 tokens + harberg.transfer(account3, 300 * 1e18); // Account 3 gets 300 tokens vm.stopPrank(); uint256 startTime = block.timestamp; @@ -252,33 +302,33 @@ contract HarbTest is Test { // Simulate different holding periods vm.warp(block.timestamp + 10 days); // Fast forward 10 days vm.prank(account1); - harb.transfer(account2, 100 * 1e18); // Account 1 transfers 100 tokens to Account 2 + harberg.transfer(account2, 100 * 1e18); // Account 1 transfers 100 tokens to Account 2 vm.warp(block.timestamp + 20 days); // Fast forward another 20 days vm.prank(account2); - harb.transfer(account3, 100 * 1e18); // Account 2 transfers 100 tokens to Account 3 + harberg.transfer(account3, 100 * 1e18); // Account 2 transfers 100 tokens to Account 3 // Simulate tax collection after the transactions vm.startPrank(address(liquidityManager)); - harb.mint(taxAmount); - harb.transfer(taxPool, taxAmount); + harberg.mint(taxAmount); + harberg.transfer(taxPool, taxAmount); vm.stopPrank(); // Assert sumTaxCollected before claiming UBI - assertEq(harb.sumTaxCollected(), taxAmount, "Tax collected should match the tax amount transferred."); + assertEq(harberg.sumTaxCollected(), taxAmount, "Tax collected should match the tax amount transferred."); // Each account claims UBI vm.prank(account1); - harb.claimUbi(account1); + harberg.claimUbi(account1); vm.prank(account2); - harb.claimUbi(account2); + harberg.claimUbi(account2); vm.prank(account3); - harb.claimUbi(account3); + harberg.claimUbi(account3); // Assert the post-claim balances reflect the TWAB calculations { - uint256 totalDistributed = harb.balanceOf(account1) + harb.balanceOf(account2) + harb.balanceOf(account3) - initialSupply; + uint256 totalDistributed = harberg.balanceOf(account1) + harberg.balanceOf(account2) + harberg.balanceOf(account3) - initialSupply; // Tolerance setup: 0.01% of the total tax amount uint256 lowerBound = taxAmount - (taxAmount / 10000); assertTrue(totalDistributed >= lowerBound && totalDistributed <= totalDistributed, "Total distributed UBI does not match the total tax collected within an acceptable tolerance range."); @@ -300,9 +350,9 @@ contract HarbTest is Test { // Assert the post-claim balances reflect the TWAB calculations with a smaller rounding tolerance // 1 * 1e14; // 0.0001 HARB token tolerance for rounding errors - assertApproxEqRel(harb.balanceOf(account1) - 300 * 1e18, expectedBalance1, 1 * 1e14, "Account 1's balance after claiming UBI is incorrect."); - assertApproxEqRel(harb.balanceOf(account2) - 300 * 1e18, expectedBalance2, 1 * 1e14, "Account 2's balance after claiming UBI is incorrect."); - assertApproxEqRel(harb.balanceOf(account3) - 400 * 1e18, expectedBalance3, 1 * 1e14, "Account 3's balance after claiming UBI is incorrect."); + assertApproxEqRel(harberg.balanceOf(account1) - 300 * 1e18, expectedBalance1, 1 * 1e14, "Account 1's balance after claiming UBI is incorrect."); + assertApproxEqRel(harberg.balanceOf(account2) - 300 * 1e18, expectedBalance2, 1 * 1e14, "Account 2's balance after claiming UBI is incorrect."); + assertApproxEqRel(harberg.balanceOf(account3) - 400 * 1e18, expectedBalance3, 1 * 1e14, "Account 3's balance after claiming UBI is incorrect."); } function testUBIClaimWithoutAnyTaxCollected() public { @@ -311,12 +361,12 @@ contract HarbTest is Test { // Setup initial supply and allocate to user vm.startPrank(address(liquidityManager)); - harb.mint(initialSupply); - harb.transfer(user, initialSupply); + harberg.mint(initialSupply); + harberg.transfer(user, initialSupply); vm.stopPrank(); // Ensure no tax has been collected yet - assertEq(harb.sumTaxCollected(), 0, "Initial tax collected should be zero."); + assertEq(harberg.sumTaxCollected(), 0, "Initial tax collected should be zero."); // Simulate time passage to ensure TWAB is recorded vm.warp(block.timestamp + 30 days); @@ -324,13 +374,13 @@ contract HarbTest is Test { // User attempts to claim UBI vm.prank(user); vm.expectRevert("No UBI to claim."); // Assuming your contract reverts with a message when there's no UBI to claim - harb.claimUbi(user); + harberg.claimUbi(user); // Ensure the user's balance remains unchanged as no UBI should be distributed - assertEq(harb.balanceOf(user), initialSupply, "User's balance should not change after attempting to claim UBI without any taxes collected."); + assertEq(harberg.balanceOf(user), initialSupply, "User's balance should not change after attempting to claim UBI without any taxes collected."); // Check if sumTaxCollected remains zero after the claim attempt - assertEq(harb.sumTaxCollected(), 0, "No tax should be collected, and sumTaxCollected should remain zero after the claim attempt."); + assertEq(harberg.sumTaxCollected(), 0, "No tax should be collected, and sumTaxCollected should remain zero after the claim attempt."); } function testEdgeCaseWithMaximumTaxCollection() public { @@ -341,27 +391,27 @@ contract HarbTest is Test { // Setup initial supply and allocate to user vm.startPrank(address(liquidityManager)); - harb.mint(initialSupply + maxTaxAmount); - harb.transfer(account1, initialSupply); - harb.transfer(taxPool, maxTaxAmount); // Simulate tax collection at the theoretical maximum + harberg.mint(initialSupply + maxTaxAmount); + harberg.transfer(account1, initialSupply); + harberg.transfer(taxPool, maxTaxAmount); // Simulate tax collection at the theoretical maximum vm.stopPrank(); // Assert that maximum tax was collected - assertEq(harb.sumTaxCollected(), maxTaxAmount, "Max tax collected should match the max tax amount transferred."); + assertEq(harberg.sumTaxCollected(), maxTaxAmount, "Max tax collected should match the max tax amount transferred."); // Simulate time passage and UBI claim vm.warp(block.timestamp + 30 days); // Account 1 claims UBI vm.prank(account1); - harb.claimUbi(account1); + harberg.claimUbi(account1); // Check if the account's balance increased correctly uint256 expectedBalance = initialSupply + maxTaxAmount; // This assumes the entire tax pool goes to one account, simplify as needed - assertEq(harb.balanceOf(account1), expectedBalance, "Account 1's balance after claiming UBI with max tax collection is incorrect."); + assertEq(harberg.balanceOf(account1), expectedBalance, "Account 1's balance after claiming UBI with max tax collection is incorrect."); // Verify that no taxes are left unclaimed - assertEq(harb.balanceOf(taxPool), 0, "All taxes should be claimed after the UBI claim."); + assertEq(harberg.balanceOf(taxPool), 0, "All taxes should be claimed after the UBI claim."); } @@ -374,10 +424,10 @@ contract HarbTest is Test { // Setup initial supply and allocate to user vm.startPrank(address(liquidityManager)); - harb.mint(initialSupply + taxAmount); - harb.transfer(account1, initialSupply / 800); - harb.transfer(taxPool, taxAmount); // Simulate tax collection at the theoretical maximum - harb.transfer(liquidityPool, harb.balanceOf(address(liquidityManager))); + harberg.mint(initialSupply + taxAmount); + harberg.transfer(account1, initialSupply / 800); + harberg.transfer(taxPool, taxAmount); // Simulate tax collection at the theoretical maximum + harberg.transfer(liquidityPool, harberg.balanceOf(address(liquidityManager))); vm.stopPrank(); vm.warp(block.timestamp + 1 hours); @@ -386,20 +436,20 @@ contract HarbTest is Test { uint numHours = 399; // More than 365 to potentially test buffer wrapping (MAX_CARDINALITY) for (uint i = 0; i < numHours; i++) { vm.prank(liquidityPool); - harb.transfer(account1, initialSupply / 800); + harberg.transfer(account1, initialSupply / 800); vm.warp(block.timestamp + 1 hours); // Fast-forward time by one hour. } // Account 1 claims UBI vm.prank(account1); - uint256 ubiCollected = harb.claimUbi(account1); + uint256 ubiCollected = harberg.claimUbi(account1); // Check if the account's balance increased correctly uint256 expectedBalance = (initialSupply / 2) + ubiCollected; // This assumes the entire tax pool goes to one account, simplify as needed - assertApproxEqRel(harb.balanceOf(account1), expectedBalance, 1 * 1e18, "Account 1's balance after claiming UBI with max tax collection is incorrect."); + assertApproxEqRel(harberg.balanceOf(account1), expectedBalance, 1 * 1e18, "Account 1's balance after claiming UBI with max tax collection is incorrect."); // Verify that no taxes are left unclaimed - assertEq(harb.balanceOf(taxPool), 0, "All taxes should be claimed after the UBI claim."); + assertEq(harberg.balanceOf(taxPool), 0, "All taxes should be claimed after the UBI claim."); } } diff --git a/onchain/test/LiquidityManager.t.sol b/onchain/test/LiquidityManager.t.sol index 7489b5b..dd769a0 100644 --- a/onchain/test/LiquidityManager.t.sol +++ b/onchain/test/LiquidityManager.t.sol @@ -10,7 +10,7 @@ import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; import {PoolAddress, PoolKey} from "@aperture/uni-v3-lib/PoolAddress.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; -import {Harb} from "../src/Harb.sol"; +import {Harberg} from "../src/Harberg.sol"; import {Stake, ExceededAvailableStake} from "../src/Stake.sol"; import {LiquidityManager} from "../src/LiquidityManager.sol"; @@ -30,7 +30,7 @@ contract Dummy { contract LiquidityManagerTest is Test { IWETH9 weth; - Harb harb; + Harberg harberg; IUniswapV3Factory factory; Stake stake; LiquidityManager lm; @@ -103,30 +103,30 @@ contract LiquidityManagerTest is Test { } weth = IWETH9(address(new WETH())); - harb = new Harb("HARB", "HARB", tc); + harberg = new Harberg("HARB", "HARB", tc); // Check if the setup meets the required condition - if (token0shouldBeWeth == address(weth) < address(harb)) { + if (token0shouldBeWeth == address(weth) < address(harberg)) { setupComplete = true; } else { // Clear current instances for re-deployment delete weth; - delete harb; + delete harberg; retryCount++; } } require(setupComplete, "Setup failed to meet the condition after several retries"); - pool = IUniswapV3Pool(factory.createPool(address(weth), address(harb), FEE)); + pool = IUniswapV3Pool(factory.createPool(address(weth), address(harberg), FEE)); - token0isWeth = address(weth) < address(harb); + token0isWeth = address(weth) < address(harberg); initializePoolFor1Cent(address(pool)); - stake = new Stake(address(harb)); - harb.setStakingPool(address(stake)); - lm = new LiquidityManager(factoryAddress, address(weth), address(harb)); + stake = new Stake(address(harberg)); + harberg.setStakingPool(address(stake)); + lm = new LiquidityManager(factoryAddress, address(weth), address(harberg)); lm.setFeeDestination(feeDestination); - harb.setLiquidityManager(address(lm)); + harberg.setLiquidityManager(address(lm)); vm.deal(address(lm), 10 ether); createCSVHeader(); } @@ -139,10 +139,10 @@ contract LiquidityManagerTest is Test { try lm.recenter() { // Check liquidity positions after slide - (uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbFloor, uint256 harbAnchor, uint256 harbDiscovery) = checkLiquidityPositionsAfter("slide"); + (uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbergFloor, uint256 harbergAnchor, uint256 harbergDiscovery) = checkLiquidityPositionsAfter("slide"); assertGt(ethFloor, ethAnchor, "slide - Floor should hold more ETH than Anchor"); - assertGt(harbDiscovery, harbAnchor * 5, "slide - Discovery should hold more HARB than Anchor"); - assertEq(harbFloor, 0, "slide - Floor should have no HARB"); + assertGt(harbergDiscovery, harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor"); + assertEq(harbergFloor, 0, "slide - Floor should have no HARB"); assertEq(ethDiscovery, 0, "slide - Discovery should have no ETH"); } catch Error(string memory reason) { if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) { @@ -162,10 +162,10 @@ contract LiquidityManagerTest is Test { try lm.recenter() { // Check liquidity positions after shift - (uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbFloor, uint256 harbAnchor, uint256 harbDiscovery) = checkLiquidityPositionsAfter("shift"); + (uint256 ethFloor, uint256 ethAnchor, uint256 ethDiscovery, uint256 harbergFloor, uint256 harbergAnchor, uint256 harbergDiscovery) = checkLiquidityPositionsAfter("shift"); assertGt(ethFloor, ethAnchor, "shift - Floor should hold more ETH than Anchor"); - assertGt(harbDiscovery, harbAnchor * 5, "shift - Discovery should hold more HARB than Anchor"); - assertEq(harbFloor, 0, "shift - Floor should have no HARB"); + assertGt(harbergDiscovery, harbergAnchor * 5, "shift - Discovery should hold more HARB than Anchor"); + assertEq(harbergFloor, 0, "shift - Floor should have no HARB"); assertEq(ethDiscovery, 0, "shift - Discovery should have no ETH"); } catch Error(string memory reason) { if (keccak256(abi.encodePacked(reason)) == keccak256(abi.encodePacked("amplitude not reached."))) { @@ -177,7 +177,7 @@ contract LiquidityManagerTest is Test { } - function getBalancesPool(LiquidityManager.Stage s) internal view returns (int24 currentTick, int24 tickLower, int24 tickUpper, uint256 ethAmount, uint256 harbAmount) { + function getBalancesPool(LiquidityManager.Stage s) internal view returns (int24 currentTick, int24 tickLower, int24 tickUpper, uint256 ethAmount, uint256 harbergAmount) { (,tickLower, tickUpper) = lm.positions(s); (uint128 liquidity, , , ,) = pool.positions(keccak256(abi.encodePacked(address(lm), tickLower, tickUpper))); @@ -192,46 +192,46 @@ contract LiquidityManagerTest is Test { if (currentTick < tickLower) { // Current price is below the lower bound of the liquidity position ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); - harbAmount = 0; // All liquidity is in token0 (ETH) + harbergAmount = 0; // All liquidity is in token0 (ETH) } else if (currentTick > tickUpper) { // Current price is above the upper bound of the liquidity position ethAmount = 0; // All liquidity is in token1 (HARB) - harbAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + harbergAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); } else { // Current price is within the bounds of the liquidity position ethAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); - harbAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); + harbergAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); } } else { if (currentTick < tickLower) { // Current price is below the lower bound of the liquidity position - harbAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); + harbergAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); ethAmount = 0; // All liquidity is in token1 (ETH) } else if (currentTick > tickUpper) { // Current price is above the upper bound of the liquidity position - harbAmount = 0; // All liquidity is in token0 (HARB) + harbergAmount = 0; // All liquidity is in token0 (HARB) ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity); } else { // Current price is within the bounds of the liquidity position - harbAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); + harbergAmount = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity); ethAmount = LiquidityAmounts.getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity); } } } // csv = "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb"; - function checkLiquidityPositionsAfter(string memory eventName) internal returns (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbFloor, uint harbAnchor, uint harbDiscovery) { + function checkLiquidityPositionsAfter(string memory eventName) internal returns (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbergFloor, uint harbergAnchor, uint harbergDiscovery) { int24 currentTick; int24 tickLower; int24 tickUpper; - (currentTick, tickLower, tickUpper, ethFloor, harbFloor) = getBalancesPool(LiquidityManager.Stage.FLOOR); - string memory floorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethFloor), ",", uintToStr(harbFloor), ",")); + (currentTick, tickLower, tickUpper, ethFloor, harbergFloor) = getBalancesPool(LiquidityManager.Stage.FLOOR); + string memory floorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethFloor), ",", uintToStr(harbergFloor), ",")); - (,tickLower, tickUpper, ethAnchor, harbAnchor) = getBalancesPool(LiquidityManager.Stage.ANCHOR); - string memory anchorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethAnchor), ",", uintToStr(harbAnchor), ",")); + (,tickLower, tickUpper, ethAnchor, harbergAnchor) = getBalancesPool(LiquidityManager.Stage.ANCHOR); + string memory anchorData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethAnchor), ",", uintToStr(harbergAnchor), ",")); - (,tickLower, tickUpper, ethDiscovery, harbDiscovery) = getBalancesPool(LiquidityManager.Stage.DISCOVERY); - string memory discoveryData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethDiscovery), ",", uintToStr(harbDiscovery), ",")); + (,tickLower, tickUpper, ethDiscovery, harbergDiscovery) = getBalancesPool(LiquidityManager.Stage.DISCOVERY); + string memory discoveryData = string(abi.encodePacked(intToStr(tickLower), ",", intToStr(tickUpper), ",", uintToStr(ethDiscovery), ",", uintToStr(harbergDiscovery), ",")); csv = string(abi.encodePacked(csv, "\n", eventName, ",", intToStr(currentTick), ",", floorData, anchorData, discoveryData)); } @@ -262,11 +262,11 @@ contract LiquidityManagerTest is Test { limit = token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1; } else { // vm.startPrank(address(lm)); - // harb.mint(amount); - // harb.transfer(address(account), amount); + // harberg.mint(amount); + // harberg.transfer(address(account), amount); // vm.stopPrank(); vm.prank(account); - harb.approve(address(this), amount); + harberg.approve(address(this), amount); limit = !token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1; } pool.swap( @@ -300,7 +300,7 @@ contract LiquidityManagerTest is Test { return; } - require(harb.transferFrom(seller, msg.sender, amountToPay), "reason 3"); + require(harberg.transferFrom(seller, msg.sender, amountToPay), "reason 3"); } receive() external payable {} @@ -368,6 +368,48 @@ contract LiquidityManagerTest is Test { vm.writeFile(path, csv); } + function testHandleCumulativeOverflow() public { + setUpCustomToken0(false); + vm.deal(account, 201 ether); + vm.prank(account); + weth.deposit{value: 201 ether}(); + + // Setup initial liquidity + slide(false); + + vm.store( + address(lm), + bytes32(uint256(0)), + bytes32(uint256(type(uint256).max - 10)) + ); + + vm.store( + address(lm), + bytes32(uint256(1)), + bytes32(uint256((type(uint256).max - 10) / (3000 * 10**20))) + ); + + + uint256 cumulativeVolumeWeightedPrice = lm.cumulativeVolumeWeightedPrice(); + uint256 beforeCumulativeVolume = lm.cumulativeVolume(); + + assertGt(cumulativeVolumeWeightedPrice, type(uint256).max / 2, "Initial cumulativeVolumeWeightedPrice is not near max uint256"); + + buy(50 ether); + + shift(); + + cumulativeVolumeWeightedPrice = lm.cumulativeVolumeWeightedPrice(); + uint256 cumulativeVolume = lm.cumulativeVolume(); + + // Assert that the values after wrap-around are valid and smaller than max uint256 + assertGt(beforeCumulativeVolume, cumulativeVolume, "cumulativeVolume after wrap-around is smaller than before"); + + // Assert that the price is reasonable + uint256 calculatedPrice = cumulativeVolumeWeightedPrice / cumulativeVolume; + assertTrue(calculatedPrice > 0 && calculatedPrice < 10**40, "Calculated price after wrap-around is not within a reasonable range"); + } + // function testLiquidityPositions() public { // setUpCustomToken0(false); @@ -375,15 +417,15 @@ contract LiquidityManagerTest is Test { // slide(); // // Initial checks of liquidity positions - // (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbFloor, uint harbAnchor, uint harbDiscovery) = checkLiquidityPositionsAfter("slide"); + // (uint ethFloor, uint ethAnchor, uint ethDiscovery, uint harbergFloor, uint harbergAnchor, uint harbergDiscovery) = checkLiquidityPositionsAfter("slide"); // // Assertions to verify initial setup // assertGt(ethFloor, 9 ether, "Floor should have initial ETH"); // assertGt(ethAnchor, 0.8 ether, "Anchor should have initial ETH"); // assertEq(ethDiscovery, 0, "Discovery should not have ETH"); - // assertEq(harbFloor, 0, "Floor should have no HARB"); - // assertGt(harbAnchor, 0, "Anchor should have HARB"); - // assertGt(harbDiscovery, 0, "Discovery should have HARB"); + // assertEq(harbergFloor, 0, "Floor should have no HARB"); + // assertGt(harbergAnchor, 0, "Anchor should have HARB"); + // assertGt(harbergDiscovery, 0, "Discovery should have HARB"); // // Introduce large buy to push into discovery // buy(3 ether); @@ -396,30 +438,30 @@ contract LiquidityManagerTest is Test { // shift(); // // Check liquidity positions after shift - // (ethFloor, ethAnchor, ethDiscovery, harbFloor, , ) = checkLiquidityPositionsAfter("shift"); + // (ethFloor, ethAnchor, ethDiscovery, harbergFloor, , ) = checkLiquidityPositionsAfter("shift"); // assertGt(ethFloor, 11.5 ether, "Floor should have more ETH"); // assertGt(ethAnchor, 1.4 ether, "Anchor should have more ETH"); // assertEq(ethDiscovery, 0, "Discovery should not have ETH after shift"); - // assertEq(harbFloor, 0, "Floor should have no HARB"); + // assertEq(harbergFloor, 0, "Floor should have no HARB"); // // Simulate large sell to push price down to floor // sell(6 * 10**23); // // Check liquidity positions after sell - // (ethFloor, ethAnchor, ethDiscovery, harbFloor, harbAnchor, harbDiscovery) = checkLiquidityPositionsAfter("sell 600000"); + // (ethFloor, ethAnchor, ethDiscovery, harbergFloor, harbergAnchor, harbergDiscovery) = checkLiquidityPositionsAfter("sell 600000"); // assertGt(ethFloor, 0, "Floor should still have ETH after manipulation"); - // assertGt(harbDiscovery, 0, "Discovery should have increased HARB after buys"); + // assertGt(harbergDiscovery, 0, "Discovery should have increased HARB after buys"); // slide(); // // Check liquidity positions after slide - // (ethFloor, ethAnchor, ethDiscovery, harbFloor, harbAnchor, harbDiscovery) = checkLiquidityPositionsAfter("slide"); + // (ethFloor, ethAnchor, ethDiscovery, harbergFloor, harbergAnchor, harbergDiscovery) = checkLiquidityPositionsAfter("slide"); // // Ensure liquidity positions are as expected // assertGt(ethFloor, 0, "Floor should still have ETH after manipulation"); - // assertEq(harbFloor, 0, "Floor should have no HARB"); + // assertEq(harbergFloor, 0, "Floor should have no HARB"); // assertEq(ethDiscovery, 0, "Discovery should not have ETH after slide"); - // assertGt(harbDiscovery, 0, "Discovery should have increased HARB after buys"); + // assertGt(harbergDiscovery, 0, "Discovery should have increased HARB after buys"); // writeCsv(); // } @@ -440,7 +482,7 @@ contract LiquidityManagerTest is Test { // //revert(); - // sell(harb.balanceOf(account)); + // sell(harberg.balanceOf(account)); // slide(true); @@ -478,7 +520,7 @@ contract LiquidityManagerTest is Test { // shift(); - // sell(harb.balanceOf(account)); + // sell(harberg.balanceOf(account)); // slide(true); @@ -508,20 +550,20 @@ contract LiquidityManagerTest is Test { uint8 f = 0; for (uint i = 0; i < numActions; i++) { uint256 amount = (uint256(amounts[i]) * 1 ether) + 1 ether; - uint256 harbBal = harb.balanceOf(account); - if (harbBal == 0) { + uint256 harbergBal = harberg.balanceOf(account); + if (harbergBal == 0) { amount = amount % (weth.balanceOf(account) / 2); amount = amount == 0 ? weth.balanceOf(account) : amount; buy(amount); } else if (weth.balanceOf(account) == 0) { - sell(amount % harbBal); + sell(amount % harbergBal); } else { if (amount % 2 == 0) { amount = amount % (weth.balanceOf(account) / 2); amount = amount == 0 ? weth.balanceOf(account) : amount; buy(amount); } else { - sell(amount % harbBal); + sell(amount % harbergBal); } } @@ -551,7 +593,7 @@ contract LiquidityManagerTest is Test { } // Simulate large sell to push price down to floor - sell(harb.balanceOf(account)); + sell(harberg.balanceOf(account)); slide(true); diff --git a/onchain/test/Stake.t.sol b/onchain/test/Stake.t.sol index 413bebe..329cb2f 100644 --- a/onchain/test/Stake.t.sol +++ b/onchain/test/Stake.t.sol @@ -4,31 +4,31 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "forge-std/console.sol"; import {TwabController} from "pt-v5-twab-controller/TwabController.sol"; -import "../src/Harb.sol"; +import "../src/Harberg.sol"; import {TooMuchSnatch, Stake} from "../src/Stake.sol"; contract StakeTest is Test { TwabController tc; - Harb harb; + Harberg harberg; Stake stakingPool; address liquidityPool; address liquidityManager; address taxPool; - event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 harbDeposit, uint256 share, uint32 taxRate); - event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 harbPayout); + event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 harbergDeposit, uint256 share, uint32 taxRate); + event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 harbergPayout); function setUp() public { tc = new TwabController(60 * 60, uint32(block.timestamp)); - harb = new Harb("HARB", "HARB", tc); - taxPool = harb.TAX_POOL(); - stakingPool = new Stake(address(harb)); - harb.setStakingPool(address(stakingPool)); + harberg = new Harberg("HARB", "HARB", tc); + taxPool = harberg.TAX_POOL(); + stakingPool = new Stake(address(harberg)); + harberg.setStakingPool(address(stakingPool)); liquidityPool = makeAddr("liquidityPool"); - harb.setLiquidityPool(liquidityPool); + harberg.setLiquidityPool(liquidityPool); liquidityManager = makeAddr("liquidityManager"); - harb.setLiquidityManager(liquidityManager); + harberg.setLiquidityManager(liquidityManager); } @@ -38,14 +38,14 @@ contract StakeTest is Test { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(stakeAmount * 5); - harb.transfer(staker, stakeAmount); + harberg.mint(stakeAmount * 5); + harberg.transfer(staker, stakeAmount); vm.stopPrank(); vm.startPrank(staker); // Approve and stake - harb.approve(address(stakingPool), stakeAmount); + harberg.approve(address(stakingPool), stakeAmount); uint256[] memory empty; uint256 sharesExpected = stakingPool.assetsToShares(stakeAmount); vm.expectEmit(address(stakingPool)); @@ -69,13 +69,13 @@ contract StakeTest is Test { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(stakeAmount * 5); // Ensuring the staker has enough balance - harb.transfer(staker, stakeAmount); + harberg.mint(stakeAmount * 5); // Ensuring the staker has enough balance + harberg.transfer(staker, stakeAmount); vm.stopPrank(); // Staker stakes tokens vm.startPrank(staker); - harb.approve(address(stakingPool), stakeAmount); + harberg.approve(address(stakingPool), stakeAmount); uint256[] memory empty; uint256 positionId = stakingPool.snatch(stakeAmount, staker, 1, empty); @@ -94,7 +94,7 @@ contract StakeTest is Test { stakingPool.exitPosition(positionId); // Check results after unstaking - assertEq(harb.balanceOf(staker), assetsAfterTax, "Assets after tax not returned correctly"); + assertEq(harberg.balanceOf(staker), assetsAfterTax, "Assets after tax not returned correctly"); assertEq(stakingPool.outstandingStake(), 0, "Outstanding stake not updated correctly"); // Ensure the position is cleared @@ -115,10 +115,10 @@ contract StakeTest is Test { // Mint and distribute tokens vm.startPrank(liquidityManager); - harb.mint((initialStake1 + initialStake2) * 5); - harb.transfer(firstStaker, initialStake1); - harb.transfer(secondStaker, initialStake2); - harb.transfer(newStaker, snatchAmount); + harberg.mint((initialStake1 + initialStake2) * 5); + harberg.transfer(firstStaker, initialStake1); + harberg.transfer(secondStaker, initialStake2); + harberg.transfer(newStaker, snatchAmount); vm.stopPrank(); // Setup initial stakers @@ -127,7 +127,7 @@ contract StakeTest is Test { // Snatch setup vm.startPrank(newStaker); - harb.approve(address(stakingPool), snatchAmount); + harberg.approve(address(stakingPool), snatchAmount); uint256 snatchShares = stakingPool.assetsToShares(snatchAmount); uint256[] memory targetPositions = new uint256[](2); targetPositions[0] = positionId1; @@ -148,7 +148,7 @@ contract StakeTest is Test { function setupStaker(address staker, uint256 amount, uint32 taxRate) private returns (uint256 positionId) { vm.startPrank(staker); - harb.approve(address(stakingPool), amount); + harberg.approve(address(stakingPool), amount); uint256[] memory empty; positionId = stakingPool.snatch(amount, staker, taxRate, empty); vm.stopPrank(); @@ -172,16 +172,16 @@ contract StakeTest is Test { function testRevert_SharesTooLow() public { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(10 ether); - uint256 tooSmallStake = harb.previousTotalSupply() / 4000; // Less than minStake calculation - harb.transfer(staker, tooSmallStake); + harberg.mint(10 ether); + uint256 tooSmallStake = harberg.previousTotalSupply() / 4000; // Less than minStake calculation + harberg.transfer(staker, tooSmallStake); vm.stopPrank(); vm.startPrank(staker); - harb.approve(address(stakingPool), tooSmallStake); + harberg.approve(address(stakingPool), tooSmallStake); uint256[] memory empty; - vm.expectRevert(abi.encodeWithSelector(Stake.StakeTooLow.selector, staker, tooSmallStake, harb.previousTotalSupply() / 3000)); + vm.expectRevert(abi.encodeWithSelector(Stake.StakeTooLow.selector, staker, tooSmallStake, harberg.previousTotalSupply() / 3000)); stakingPool.snatch(tooSmallStake, staker, 1, empty); vm.stopPrank(); } @@ -190,16 +190,16 @@ contract StakeTest is Test { address existingStaker = makeAddr("existingStaker"); address newStaker = makeAddr("newStaker"); vm.startPrank(liquidityManager); - harb.mint(10 ether); - harb.transfer(existingStaker, 1 ether); - harb.transfer(newStaker, 1 ether); + harberg.mint(10 ether); + harberg.transfer(existingStaker, 1 ether); + harberg.transfer(newStaker, 1 ether); vm.stopPrank(); uint256 positionId = setupStaker(existingStaker, 1 ether, 5); // Existing staker with tax rate 5 vm.startPrank(newStaker); - harb.transfer(newStaker, 1 ether); - harb.approve(address(stakingPool), 1 ether); + harberg.transfer(newStaker, 1 ether); + harberg.approve(address(stakingPool), 1 ether); uint256[] memory positions = new uint256[](1); positions[0] = positionId; // Assuming position ID 1 has tax rate 5 @@ -214,15 +214,15 @@ contract StakeTest is Test { address ambitiousStaker = makeAddr("ambitiousStaker"); vm.startPrank(liquidityManager); - harb.mint(20 ether); - harb.transfer(staker, 2 ether); - harb.transfer(ambitiousStaker, 1 ether); + harberg.mint(20 ether); + harberg.transfer(staker, 2 ether); + harberg.transfer(ambitiousStaker, 1 ether); vm.stopPrank(); uint256 positionId = setupStaker(staker, 2 ether, 10); vm.startPrank(ambitiousStaker); - harb.approve(address(stakingPool), 1 ether); + harberg.approve(address(stakingPool), 1 ether); uint256[] memory positions = new uint256[](1); positions[0] = positionId; @@ -235,12 +235,12 @@ contract StakeTest is Test { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(10 ether); - harb.transfer(staker, 1 ether); + harberg.mint(10 ether); + harberg.transfer(staker, 1 ether); vm.stopPrank(); vm.startPrank(staker); - harb.approve(address(stakingPool), 1 ether); + harberg.approve(address(stakingPool), 1 ether); uint256[] memory nonExistentPositions = new uint256[](1); nonExistentPositions[0] = 999; // Assumed non-existent position ID @@ -256,12 +256,12 @@ contract StakeTest is Test { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(10 ether); - harb.transfer(staker, 1 ether); + harberg.mint(10 ether); + harberg.transfer(staker, 1 ether); vm.stopPrank(); vm.startPrank(staker); - harb.approve(address(stakingPool), 1 ether); + harberg.approve(address(stakingPool), 1 ether); uint256[] memory empty; uint256 positionId = stakingPool.snatch(1 ether, staker, 1, empty); @@ -287,12 +287,12 @@ contract StakeTest is Test { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(10 ether); - harb.transfer(staker, 1 ether); + harberg.mint(10 ether); + harberg.transfer(staker, 1 ether); vm.stopPrank(); vm.startPrank(staker); - harb.approve(address(stakingPool), 1 ether); + harberg.approve(address(stakingPool), 1 ether); uint256[] memory empty; uint256 positionId = stakingPool.snatch(1 ether, staker, 5, empty); // Using tax rate index 5, which is 18% per year (uint256 shareBefore, , , , ) = stakingPool.positions(positionId); @@ -327,12 +327,12 @@ contract StakeTest is Test { address staker = makeAddr("staker"); vm.startPrank(liquidityManager); - harb.mint(10 ether); - harb.transfer(staker, 1 ether); + harberg.mint(10 ether); + harberg.transfer(staker, 1 ether); vm.stopPrank(); vm.startPrank(staker); - harb.approve(address(stakingPool), 1 ether); + harberg.approve(address(stakingPool), 1 ether); uint256[] memory empty; uint256 positionId = stakingPool.snatch(1 ether, staker, 12, empty); // Using tax rate index 5, which is 100% per year vm.warp(block.timestamp + 365 days); // Move time forward to ensure maximum tax due