This commit is contained in:
JulesCrown 2024-03-12 12:27:47 +01:00
parent 854c26a623
commit 307f98840b
6 changed files with 42 additions and 31 deletions

View file

@ -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));

View file

@ -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);

View file

@ -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

View file

@ -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);
}

View file

@ -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);

View file

@ -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