diff --git a/onchain/README.md b/onchain/README.md index c860d7b..0367326 100644 --- a/onchain/README.md +++ b/onchain/README.md @@ -112,15 +112,7 @@ address: 0xCc7467616bBDB574D04C7e9d2B0982c59F33D43c ## References - take percentage math from here: https://github.com/attestate/libharberger/tree/master -- implement this ERC for Harb: https://eips.ethereum.org/EIPS/eip-4907 - - TaxHouse contract - erc721 owner - 20% of supply - - implement auction model with tax - - instrument holder is user in ERC4907 - - 5% of supply founder and influencers - - direct ERC721 ownernership - add this function: https://github.com/721labs/partial-common-ownership/blob/3e7713bc60b6bb2e103320036ec5aeaaaceb7d2b/contracts/token/modules/Taxation.sol#L260 -- address this issue: "Seems like an owner could always frontrun buy attempts by increasing the valuation by one wei." -- rename TAX_FLOOR_DURATION to cooldown? - limit discovery position growth to max_issuance / day open features: @@ -147,11 +139,11 @@ open features: - mint - limit supply to 2^96? - Stake - what if someone calls payTax and exitPosition in the same transaction? - - - - LiquidityManager + + - LiquidityManager - what to do with stuck funds if slide/shift become inoperable? - - _isPriceStable - // Handle try catch, possibly by trying with a different time interval or providing a default response - - make slide and shift one function + - _isPriceStable - // Handle try catch, possibly by trying with a different time interval or providing a default response\ + - test wraparound of vwap - NFT support of etherscan diff --git a/onchain/src/Harb.sol b/onchain/src/Harb.sol index fc65ec5..c748d39 100644 --- a/onchain/src/Harb.sol +++ b/onchain/src/Harb.sol @@ -19,6 +19,11 @@ contract Harb is ERC20, ERC20Permit { // only working with 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. 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. @@ -35,6 +40,7 @@ contract Harb is ERC20, ERC20Permit { /* ============ Public Variables ============ */ uint256 public sumTaxCollected; uint256 public previousTotalSupply; + uint256 public minStakeSupplyFraction; struct UbiTitle { @@ -70,6 +76,7 @@ contract Harb is ERC20, ERC20Permit { twabController = twabController_; PERIOD_OFFSET = twabController.PERIOD_OFFSET(); PERIOD_LENGTH = twabController.PERIOD_LENGTH(); + minStakeSupplyFraction = MIN_STAKE_FRACTION; } /// @notice Sets the address for the liquidityPool. Used once post-deployment to initialize the contract. @@ -106,6 +113,10 @@ contract Harb is ERC20, ERC20Permit { return (address(twabController), liquidityManager, stakingPool, liquidityPool); } + 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. @@ -129,6 +140,12 @@ contract Harb is ERC20, ERC20Permit { previousTotalSupply = _ts; } + function setMinStakeSupplyFraction(uint256 _mssf) external onlyLiquidityManager { + require(_mssf >= MIN_STAKE_FRACTION, "minStakeSupplyFraction below allowed min"); + require(_mssf <= MAX_STAKE_FRACTION, "minStakeSupplyFraction above allow max"); + minStakeSupplyFraction = _mssf; + } + /* ============ Public ERC20 Overrides ============ */ /// @inheritdoc ERC20 diff --git a/onchain/src/LiquidityManager.sol b/onchain/src/LiquidityManager.sol index 082e8ec..95f091b 100644 --- a/onchain/src/LiquidityManager.sol +++ b/onchain/src/LiquidityManager.sol @@ -145,6 +145,10 @@ contract LiquidityManager { capitalInfefficiency = capitalInfefficiency_; } + function setMinStakeSupplyFraction(uint256 mssf_) external onlyFeeDestination { + harb.setMinStakeSupplyFraction(mssf_); + } + receive() external payable { } diff --git a/onchain/src/Stake.sol b/onchain/src/Stake.sol index 6a316d0..5471c24 100644 --- a/onchain/src/Stake.sol +++ b/onchain/src/Stake.sol @@ -36,7 +36,6 @@ contract Stake { // only 20% of the total HARB supply can be staked. uint256 internal constant MAX_STAKE = 20; // 20% of HARB supply uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3; //this duration is the minimum basis for fee calculation, regardless of actual holding time. - uint256 internal constant MIN_SUPPLY_FRACTION = 3000; // the tax rates are discrete to prevent users from snatching by micro incroments of tax uint256[] public TAX_RATES = [1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700]; // this is the base for the values in the array above: e.g. 1/100 = 1% @@ -64,11 +63,13 @@ contract Stake { uint32 taxRate; // e.g. value of 60 = 60% tax per year } - uint256 public immutable totalSupply; Harb private immutable harb; address private immutable taxPool; + + uint256 public immutable totalSupply; uint256 public outstandingStake; uint256 public nextPositionId; + mapping(uint256 => StakingPosition) public positions; /// @notice Initializes the stake contract with references to the Harb contract and sets the initial position ID. @@ -76,9 +77,9 @@ contract Stake { /// @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(); + // start counting somewhere nextPositionId = 654321; } @@ -171,7 +172,7 @@ contract Stake { { // check that position size is at least minStake // to prevent excessive fragmentation, increasing snatch cost - uint256 minStake = harb.previousTotalSupply() / MIN_SUPPLY_FRACTION; + uint256 minStake = harb.minStake(); if (assets < minStake) { revert StakeTooLow(receiver, assets, minStake); } @@ -340,4 +341,6 @@ contract Stake { uint256 assetsBefore = sharesToAssets(pos.share); amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE; } + + }