added overflow checks
This commit is contained in:
parent
dbc23802d2
commit
b243874f02
7 changed files with 493 additions and 327 deletions
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue