From 307f98840bb8149dca16a0c84bc987be05fad22a Mon Sep 17 00:00:00 2001 From: JulesCrown Date: Tue, 12 Mar 2024 12:27:47 +0100 Subject: [PATCH] wip --- onchain/script/Deploy.sol | 4 +++- onchain/src/Harb.sol | 10 ++++---- onchain/src/LiquidityManager.sol | 2 +- onchain/src/Stake.sol | 41 +++++++++++++++++--------------- onchain/src/interfaces/IHarb.sol | 4 +++- onchain/test/Harb.t.sol | 12 ++++++---- 6 files changed, 42 insertions(+), 31 deletions(-) diff --git a/onchain/script/Deploy.sol b/onchain/script/Deploy.sol index 6292fc8..bbfcb37 100644 --- a/onchain/script/Deploy.sol +++ b/onchain/script/Deploy.sol @@ -1,6 +1,7 @@ pragma solidity ^0.8.4; import "forge-std/Script.sol"; +import { TwabController } from "pt-v5-twab-controller/TwabController.sol"; import "../src/Harb.sol"; import "../src/Stake.sol"; @@ -12,7 +13,8 @@ contract GoerliScript is Script { uint256 privateKey = vm.deriveKey(seedPhrase, 0); vm.startBroadcast(privateKey); - Harb harb = new Harb("Harberger Tax", "HARB"); + TwabController tc = new TwabController(60*60*24, uint32(block.timestamp)); + Harb harb = new Harb("Harberger Tax", "HARB", tc); Stake stake = new Stake(address(harb)); harb.setStakingPool(address(stake)); diff --git a/onchain/src/Harb.sol b/onchain/src/Harb.sol index e9849bf..92ff556 100644 --- a/onchain/src/Harb.sol +++ b/onchain/src/Harb.sol @@ -72,14 +72,14 @@ contract Harb is ERC20, ERC20Permit { /// @dev May be overridden to provide more granular control over minting /// @param _amount Amount of tokens to mint function mint(uint256 _amount) external onlyLiquidityManager { - _mintHarb(liquidityManager, _amount); + _mint(liquidityManager, _amount); } /// @notice Allows the liquidityManager to burn tokens from a its account /// @dev May be overridden to provide more granular control over burning /// @param _amount Amount of tokens to burn function burn(uint256 _amount) external onlyLiquidityManager { - _burnHarb(liquidityManager, _amount); + _burn(liquidityManager, _amount); } /* ============ Public ERC20 Overrides ============ */ @@ -107,10 +107,10 @@ contract Harb is ERC20, ERC20Permit { * @param receiver Address that will receive the minted tokens * @param amount Tokens to mint */ - function _mintHarb(address receiver, uint256 amount) internal { + function _mint(address receiver, uint256 amount) internal virtual override { // make sure staking pool grows proportional to economy uint256 stakingPoolBalance = balanceOf(stakingPool); - uint256 activeSupply = totalSupply - stakingPoolBalance; + uint256 activeSupply = totalSupply() - stakingPoolBalance; uint256 dormantStake = IStake(stakingPool).dormantSupply(); if (stakingPoolBalance > 0) { uint256 newStake = stakingPoolBalance * amount / (activeSupply + dormantStake); @@ -129,7 +129,7 @@ contract Harb is ERC20, ERC20Permit { * @param _owner The owner of the tokens * @param _amount The amount of tokens to burn */ - function _burnHarb(address _owner, uint256 _amount) internal { + function _burn(address _owner, uint256 _amount) internal virtual override { // TODO twabController.burn(_owner, SafeCast.toUint96(_amount)); emit Transfer(_owner, address(0), _amount); diff --git a/onchain/src/LiquidityManager.sol b/onchain/src/LiquidityManager.sol index 919e52e..cefb2ff 100644 --- a/onchain/src/LiquidityManager.sol +++ b/onchain/src/LiquidityManager.sol @@ -8,7 +8,7 @@ import '@aperture/uni-v3-lib/LiquidityAmounts.sol'; import '@aperture/uni-v3-lib/PoolAddress.sol'; import '@aperture/uni-v3-lib/CallbackValidation.sol'; import '@uniswap-v3-core/interfaces/IUniswapV3Pool.sol'; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import '@openzeppelin/token/ERC20/IERC20.sol'; /** * @title LiquidityManager - A contract that supports the $bloodX ecosystem. It diff --git a/onchain/src/Stake.sol b/onchain/src/Stake.sol index 4b8b3b0..f80c238 100644 --- a/onchain/src/Stake.sol +++ b/onchain/src/Stake.sol @@ -32,7 +32,7 @@ contract Stake is IStake { } uint256 public immutable totalSupply; - address private immutable tokenContract; + IERC20 private immutable tokenContract; address private immutable taxPool; uint256 public outstandingStake; uint256 private lastTokenId; @@ -43,7 +43,7 @@ contract Stake is IStake { constructor( address _tokenContract ) { - tokenContract = _tokenContract; + tokenContract = IERC20(_tokenContract); IHarb harb = IHarb(_tokenContract); totalSupply = 100 * 10 ** 5 * harb.decimals(); taxPool = harb.taxPool(); @@ -58,11 +58,11 @@ contract Stake is IStake { } function assetsToShares(uint256 assets) private view returns (uint256) { - return assets * totalSupply / IERC20(tokenContract).totalSupply(); + return assets * totalSupply / tokenContract.totalSupply(); } function sharesToAssets(uint256 shares) private view returns (uint256) { - return shares * IERC20(tokenContract).totalSupply() / totalSupply; + return shares * tokenContract.totalSupply() / totalSupply; } /** @@ -73,7 +73,7 @@ contract Stake is IStake { * paying for execution may not be the actual sender (as far as an application * is concerned). */ - function snatch(uint256 assets, address receiver, uint64 taxRate, uint256[] calldata positionsToSnatch) public returns(uint256) { + function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) public returns(uint256) { // check lower boundary uint256 sharesWanted = assetsToShares(assets); @@ -89,15 +89,16 @@ contract Stake is IStake { revert PositionNotFound(); } // check that tax lower - if (taxRate <= pos.perSecondTaxRate) { - revert TaxTooLow(receiver, taxRate, pos.perSecondTaxRate, i); + if (taxRate <= pos.taxRate) { + revert TaxTooLow(receiver, taxRate, pos.taxRate, i); } // dissolve position - _payTax(pos); + // TODO: what if someone calls payTax and exitPosition in the same transaction? + _payTax(pos, 0); _exitPosition(pos); } - // now try to make a new position in the free space and hope it is big enough + // try to make a new position in the free space and hope it is big enough uint256 availableStake = authorizedStake() - outstandingStake; if (sharesWanted > availableStake) { revert ExceededAvailableStake(receiver, sharesWanted, availableStake); @@ -110,9 +111,9 @@ contract Stake is IStake { StakingPosition storage sp = positions[lastTokenId++]; sp.share = sharesWanted; sp.owner = receiver; - sp.lastTaxTime = now; - sp.creationTime = now; - sp.perSecondTaxRate = taxRate; + sp.lastTaxTime = uint32(block.timestamp); + sp.creationTime = uint32(block.timestamp); + sp.taxRate = taxRate; outstandingStake += sharesWanted; @@ -129,17 +130,17 @@ contract Stake is IStake { _payTax(pos, TAX_FLOOR_DURATION); _exitPosition(pos); } - - // TODO: what if someone calls payTax and exitPosition in the same transaction? + function payTax(uint256 positionID) public { StakingPosition storage pos = positions[positionID]; + // TODO: what if someone calls payTax and exitPosition in the same transaction? _payTax(pos, 0); } function _payTax(StakingPosition storage pos, uint256 taxFloorDuration) private { // ihet = Implied Holding Expiry Timestamp - uint256 ihet = (now - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : now; + uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp; uint256 elapsedTime = ihet - pos.lastTaxTime; uint256 assetsBefore = sharesToAssets(pos.share); uint256 taxDue = assetsBefore * pos.taxRate * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE; @@ -150,13 +151,14 @@ contract Stake is IStake { SafeERC20.safeTransfer(tokenContract, taxPool, taxDue); if (assetsBefore - taxDue > 0) { // if something left over, update storage - pos.shares = assetsToShares(assetsBefore - taxDue); - pos.lastTaxTime = now; + pos.share = assetsToShares(assetsBefore - taxDue); + pos.lastTaxTime = uint32(block.timestamp); } else { // if nothing left over, liquidate position // TODO: emit event outstandingStake -= pos.share; - delete pos; + delete pos.owner; + delete pos.creationTime; } } @@ -164,7 +166,8 @@ contract Stake is IStake { outstandingStake -= pos.share; address owner = pos.owner; uint256 assets = sharesToAssets(pos.share); - delete pos; + delete pos.owner; + delete pos.creationTime; SafeERC20.safeTransfer(tokenContract, owner, assets); } diff --git a/onchain/src/interfaces/IHarb.sol b/onchain/src/interfaces/IHarb.sol index 0d75000..6fea33a 100644 --- a/onchain/src/interfaces/IHarb.sol +++ b/onchain/src/interfaces/IHarb.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.13; -interface IHarb { +import { IERC20Metadata } from "@openzeppelin/token/ERC20/ERC20.sol"; + +interface IHarb is IERC20Metadata { function taxPool() external view returns(address); diff --git a/onchain/test/Harb.t.sol b/onchain/test/Harb.t.sol index bd709a5..18ea792 100644 --- a/onchain/test/Harb.t.sol +++ b/onchain/test/Harb.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; 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/Stake.sol"; @@ -12,7 +13,10 @@ contract BloodXTest is Test { uint256 constant MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; function setUp() public { - harb = new Harb("name", "SYM"); + + TwabController tc = new TwabController(60*60*24, uint32(block.timestamp)); + + harb = new Harb("name", "SYM", tc); stake = new Stake(address(harb)); harb.setStakingPool(address(stake)); } @@ -27,7 +31,7 @@ contract BloodXTest is Test { // test mint uint256 totalSupplyBefore = harb.totalSupply(); uint256 balanceBefore = harb.balanceOf(account); - harb.purchase(account, amount); + harb.mint(amount); uint256 totalAfter = harb.totalSupply(); assertEq(totalAfter, totalSupplyBefore + amount, "total supply should match"); assertEq(harb.balanceOf(account), balanceBefore + amount, "balance should match"); @@ -36,13 +40,13 @@ contract BloodXTest is Test { uint256 newStake = amount / 2 * 100000 ether / totalAfter; { uint256 outstandingBefore = stake.totalSupply(); - uint256 stakeBalanceBefore = stake.balanceOf(account); + (,,,,uint256 stakeBalanceBefore) = stake.positions(1); vm.prank(account); harb.stake(account, amount / 2); assertEq(harb.totalSupply(), totalSupplyBefore + (amount - (amount / 2)), "total supply should match after stake"); assertEq(harb.balanceOf(account), balanceBefore + (amount - (amount / 2)), "balance should match after stake"); assertEq(outstandingBefore + newStake, stake.totalSupply(), "outstanding supply should match"); - assertEq(stakeBalanceBefore + newStake, stake.balanceOf(account), "balance of stake account should match"); + assertEq(stakeBalanceBefore + newStake, stake.positions(1).share, "balance of stake account should match"); } // test unstake