wip
This commit is contained in:
parent
854c26a623
commit
307f98840b
6 changed files with 42 additions and 31 deletions
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -130,16 +131,16 @@ contract Stake is IStake {
|
|||
_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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue