staking test cleanup
This commit is contained in:
parent
2b817c9331
commit
36833cab7f
10 changed files with 175 additions and 404 deletions
|
|
@ -113,9 +113,20 @@ address: 0xeB64dD1b4c0D59c9c8Cb3d9EA0E319cD0d45825f
|
||||||
- add this function: https://github.com/721labs/partial-common-ownership/blob/3e7713bc60b6bb2e103320036ec5aeaaaceb7d2b/contracts/token/modules/Taxation.sol#L260
|
- 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."
|
- 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?
|
- rename TAX_FLOOR_DURATION to cooldown?
|
||||||
- burn tokens should affect stake
|
|
||||||
- limit discovery position growth to max_issuance / day
|
- limit discovery position growth to max_issuance / day
|
||||||
|
|
||||||
|
open features:
|
||||||
|
- token minting limit / limit on discovery position growth
|
||||||
|
- ERC721 & ERC4907, user/owner separation, influencer incentives
|
||||||
|
- token contract not visible in uniswap sepolia
|
||||||
|
- snatch collision
|
||||||
|
- previousTotalSupply at beginning?
|
||||||
|
- profit for staking position
|
||||||
|
- tax paid for staking position
|
||||||
|
- partially snatched
|
||||||
|
- liquidation bot
|
||||||
|
- shift/slide bot
|
||||||
|
- ubi claim bot
|
||||||
|
|
||||||
## old lp
|
## old lp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,12 @@ contract SepoliaScript is Script {
|
||||||
|
|
||||||
TwabController tc = TwabController(TWABC);
|
TwabController tc = TwabController(TWABC);
|
||||||
//TwabController tc = new TwabController(60 * 60, uint32(block.timestamp));
|
//TwabController tc = new TwabController(60 * 60, uint32(block.timestamp));
|
||||||
Harb harb = new Harb("Harberger Tax", "HARB", V3_FACTORY, WETH, tc);
|
Harb harb = new Harb("Harberger Tax", "HARB", tc);
|
||||||
Stake stake = new Stake(address(harb));
|
Stake stake = new Stake(address(harb));
|
||||||
harb.setStakingPool(address(stake));
|
harb.setStakingPool(address(stake));
|
||||||
IUniswapV3Factory factory = IUniswapV3Factory(V3_FACTORY);
|
IUniswapV3Factory factory = IUniswapV3Factory(V3_FACTORY);
|
||||||
factory.createPool(WETH, address(harb), FEE);
|
address liquidityPool = factory.createPool(WETH, address(harb), FEE);
|
||||||
|
harb.setLiquidityPool(liquidityPool);
|
||||||
BaseLineLP liquidityManager = new BaseLineLP(V3_FACTORY, WETH, address(harb));
|
BaseLineLP liquidityManager = new BaseLineLP(V3_FACTORY, WETH, address(harb));
|
||||||
harb.setLiquidityManager(address(liquidityManager));
|
harb.setLiquidityManager(address(liquidityManager));
|
||||||
vm.stopBroadcast();
|
vm.stopBroadcast();
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,10 @@ contract BaseLineLP {
|
||||||
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
|
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function liquidityPool() external view returns (address) {
|
||||||
|
return address(pool);
|
||||||
|
}
|
||||||
|
|
||||||
function setFeeDestination(address feeDestination_) external {
|
function setFeeDestination(address feeDestination_) external {
|
||||||
// TODO: add trapdoor
|
// TODO: add trapdoor
|
||||||
require(address(0) != feeDestination_, "zero addr");
|
require(address(0) != feeDestination_, "zero addr");
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,8 @@ pragma solidity ^0.8.19;
|
||||||
import {ERC20, IERC20, IERC20Metadata} from "@openzeppelin/token/ERC20/ERC20.sol";
|
import {ERC20, IERC20, IERC20Metadata} from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||||
import {ERC20Permit, IERC20Permit} from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
import {ERC20Permit, IERC20Permit} from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||||
import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol";
|
import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol";
|
||||||
import {IStake} from "./interfaces/IStake.sol";
|
|
||||||
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
|
||||||
import "@aperture/uni-v3-lib/PoolAddress.sol";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title TWAB ERC20 Token
|
* @title TWAB ERC20 Token
|
||||||
|
|
@ -23,24 +20,20 @@ contract Harb is ERC20, ERC20Permit {
|
||||||
|
|
||||||
address public constant TAX_POOL = address(2);
|
address public constant TAX_POOL = address(2);
|
||||||
uint24 constant FEE = uint24(10_000);
|
uint24 constant FEE = uint24(10_000);
|
||||||
|
|
||||||
/* ============ Public Variables ============ */
|
|
||||||
|
|
||||||
/// @notice Address of the TwabController used to keep track of balances.
|
|
||||||
TwabController public immutable twabController;
|
|
||||||
uint256 immutable PERIOD_OFFSET;
|
uint256 immutable PERIOD_OFFSET;
|
||||||
uint256 immutable PERIOD_LENGTH;
|
uint256 immutable PERIOD_LENGTH;
|
||||||
IUniswapV3Pool immutable pool;
|
|
||||||
|
|
||||||
/// @notice Address of the LiquidityManager contract that mints and burns supply
|
|
||||||
address public liquidityManager;
|
|
||||||
|
|
||||||
address public stakingPool;
|
|
||||||
|
|
||||||
|
/* ============ Public Variables ============ */
|
||||||
uint256 public sumTaxCollected;
|
uint256 public sumTaxCollected;
|
||||||
|
|
||||||
uint256 public previousTotalSupply;
|
uint256 public previousTotalSupply;
|
||||||
|
|
||||||
|
//periphery contracts
|
||||||
|
TwabController private immutable twabController;
|
||||||
|
address private liquidityManager;
|
||||||
|
address private stakingPool;
|
||||||
|
address private liquidityPool;
|
||||||
|
|
||||||
|
|
||||||
struct UbiTitle {
|
struct UbiTitle {
|
||||||
uint256 sumTaxCollected;
|
uint256 sumTaxCollected;
|
||||||
uint256 time;
|
uint256 time;
|
||||||
|
|
@ -70,7 +63,7 @@ contract Harb is ERC20, ERC20Permit {
|
||||||
* @param name_ The name of the token
|
* @param name_ The name of the token
|
||||||
* @param symbol_ The token symbol
|
* @param symbol_ The token symbol
|
||||||
*/
|
*/
|
||||||
constructor(string memory name_, string memory symbol_, address _factory, address _WETH9, TwabController twabController_)
|
constructor(string memory name_, string memory symbol_, TwabController twabController_)
|
||||||
ERC20(name_, symbol_)
|
ERC20(name_, symbol_)
|
||||||
ERC20Permit(name_)
|
ERC20Permit(name_)
|
||||||
{
|
{
|
||||||
|
|
@ -78,10 +71,13 @@ contract Harb is ERC20, ERC20Permit {
|
||||||
twabController = twabController_;
|
twabController = twabController_;
|
||||||
PERIOD_OFFSET = twabController.PERIOD_OFFSET();
|
PERIOD_OFFSET = twabController.PERIOD_OFFSET();
|
||||||
PERIOD_LENGTH = twabController.PERIOD_LENGTH();
|
PERIOD_LENGTH = twabController.PERIOD_LENGTH();
|
||||||
PoolKey memory poolKey = PoolAddress.getPoolKey(_WETH9, address(this), FEE);
|
|
||||||
pool = IUniswapV3Pool(PoolAddress.computeAddress(_factory, poolKey));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLiquidityPool(address liquidityPool_) external {
|
||||||
|
if (address(0) == liquidityPool_) revert ZeroAddressInSetter();
|
||||||
|
if (liquidityPool != address(0)) revert AddressAlreadySet();
|
||||||
|
liquidityPool = liquidityPool_;
|
||||||
|
}
|
||||||
|
|
||||||
function setLiquidityManager(address liquidityManager_) external {
|
function setLiquidityManager(address liquidityManager_) external {
|
||||||
if (address(0) == liquidityManager_) revert ZeroAddressInSetter();
|
if (address(0) == liquidityManager_) revert ZeroAddressInSetter();
|
||||||
|
|
@ -95,6 +91,10 @@ contract Harb is ERC20, ERC20Permit {
|
||||||
stakingPool = stakingPool_;
|
stakingPool = stakingPool_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPeripheryContracts() external view returns (address, address, address, address) {
|
||||||
|
return (address(twabController), liquidityManager, stakingPool, liquidityPool);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============ External Functions ============ */
|
/* ============ External Functions ============ */
|
||||||
|
|
||||||
/// @notice Allows the liquidityManager to mint tokens for itself
|
/// @notice Allows the liquidityManager to mint tokens for itself
|
||||||
|
|
@ -211,7 +211,7 @@ contract Harb is ERC20, ERC20Permit {
|
||||||
}
|
}
|
||||||
uint256 accountTwab = twabController.getTwabBetween(address(this), _account, lastTaxClaimed, lastPeriodEndAt);
|
uint256 accountTwab = twabController.getTwabBetween(address(this), _account, lastTaxClaimed, lastPeriodEndAt);
|
||||||
uint256 stakeTwab = twabController.getTwabBetween(address(this), stakingPool, lastTaxClaimed, lastPeriodEndAt);
|
uint256 stakeTwab = twabController.getTwabBetween(address(this), stakingPool, lastTaxClaimed, lastPeriodEndAt);
|
||||||
uint256 poolTwab = twabController.getTwabBetween(address(this), address(pool), lastTaxClaimed, lastPeriodEndAt);
|
uint256 poolTwab = twabController.getTwabBetween(address(this), liquidityPool, lastTaxClaimed, lastPeriodEndAt);
|
||||||
uint256 taxTwab = twabController.getTwabBetween(address(this), TAX_POOL, lastTaxClaimed, lastPeriodEndAt);
|
uint256 taxTwab = twabController.getTwabBetween(address(this), TAX_POOL, lastTaxClaimed, lastPeriodEndAt);
|
||||||
uint256 totalSupplyTwab = twabController.getTotalSupplyTwabBetween(address(this), lastTaxClaimed, lastPeriodEndAt);
|
uint256 totalSupplyTwab = twabController.getTotalSupplyTwabBetween(address(this), lastTaxClaimed, lastPeriodEndAt);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,12 @@ import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
|
||||||
import "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
import "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||||
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
|
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
|
||||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||||
import "./interfaces/IStake.sol";
|
|
||||||
import "./Harb.sol";
|
import "./Harb.sol";
|
||||||
|
|
||||||
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
||||||
error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare);
|
error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare);
|
||||||
|
|
||||||
contract Stake is IStake {
|
contract Stake {
|
||||||
using Math for uint256;
|
using Math for uint256;
|
||||||
|
|
||||||
uint256 internal DECIMAL_OFFSET = 5 + 2;
|
uint256 internal DECIMAL_OFFSET = 5 + 2;
|
||||||
|
|
@ -28,7 +27,7 @@ contract Stake is IStake {
|
||||||
error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
|
error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
|
||||||
error SharesTooLow(address receiver, uint256 assets, uint256 sharesWanted, uint256 minStake);
|
error SharesTooLow(address receiver, uint256 assets, uint256 sharesWanted, uint256 minStake);
|
||||||
error NoPermission(address requester, address owner);
|
error NoPermission(address requester, address owner);
|
||||||
error PositionNotFound();
|
error PositionNotFound(uint256 positionId, address requester);
|
||||||
|
|
||||||
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 share, uint32 creationTime, uint32 taxRate);
|
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 share, uint32 creationTime, uint32 taxRate);
|
||||||
event TaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxAmount);
|
event TaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxAmount);
|
||||||
|
|
@ -58,23 +57,19 @@ contract Stake is IStake {
|
||||||
nextPositionId = 654321;
|
nextPositionId = 654321;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dormantSupply() public view override returns (uint256) {
|
|
||||||
return totalSupply * (100 - MAX_STAKE) / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
function authorizedStake() private view returns (uint256) {
|
function authorizedStake() private view returns (uint256) {
|
||||||
return totalSupply * MAX_STAKE / 100;
|
return totalSupply * MAX_STAKE / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assetsToShares(uint256 assets, Math.Rounding rounding) private view returns (uint256) {
|
function assetsToShares(uint256 assets) public view returns (uint256) {
|
||||||
return assets.mulDiv(totalSupply, harb.totalSupply(), rounding);
|
return assets.mulDiv(totalSupply, harb.totalSupply(), Math.Rounding.Down);
|
||||||
//return assets.mulDiv(totalSupply, harb.totalSupply() + 1, rounding);
|
//return assets.mulDiv(totalSupply, harb.totalSupply() + 1, rounding);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sharesToAssets(uint256 shares, Math.Rounding rounding) private view returns (uint256) {
|
function sharesToAssets(uint256 shares) public view returns (uint256) {
|
||||||
//return shares.mulDiv(harb.totalSupply() + 1, totalSupply, rounding);
|
//return shares.mulDiv(harb.totalSupply() + 1, totalSupply, rounding);
|
||||||
// TODO: should the average total supply be used for this calculation?
|
// TODO: should the average total supply be used for this calculation?
|
||||||
return shares.mulDiv(harb.totalSupply(), totalSupply, rounding);
|
return shares.mulDiv(harb.totalSupply(), totalSupply, Math.Rounding.Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
function permitAndSnatch(
|
function permitAndSnatch(
|
||||||
|
|
@ -108,9 +103,9 @@ contract Stake is IStake {
|
||||||
returns (uint256 positionId)
|
returns (uint256 positionId)
|
||||||
{
|
{
|
||||||
// check lower boundary
|
// check lower boundary
|
||||||
uint256 sharesWanted = assetsToShares(assets, Math.Rounding.Down);
|
uint256 sharesWanted = assetsToShares(assets);
|
||||||
{
|
{
|
||||||
// check that position size is multiple of minStake
|
// check that position size is at least minStake
|
||||||
// to prevent excessive fragmentation, increasing snatch cost
|
// to prevent excessive fragmentation, increasing snatch cost
|
||||||
uint256 minStake = harb.previousTotalSupply() / 3000;
|
uint256 minStake = harb.previousTotalSupply() / 3000;
|
||||||
if (sharesWanted < minStake) {
|
if (sharesWanted < minStake) {
|
||||||
|
|
@ -127,8 +122,7 @@ contract Stake is IStake {
|
||||||
for (uint256 i = 0; i < positionsToSnatch.length - 1; i++) {
|
for (uint256 i = 0; i < positionsToSnatch.length - 1; i++) {
|
||||||
StakingPosition storage pos = positions[positionsToSnatch[i]];
|
StakingPosition storage pos = positions[positionsToSnatch[i]];
|
||||||
if (pos.creationTime == 0) {
|
if (pos.creationTime == 0) {
|
||||||
//TODO:
|
revert PositionNotFound(positionsToSnatch[i], receiver);
|
||||||
revert PositionNotFound();
|
|
||||||
}
|
}
|
||||||
// check that tax lower
|
// check that tax lower
|
||||||
if (taxRate <= pos.taxRate) {
|
if (taxRate <= pos.taxRate) {
|
||||||
|
|
@ -151,7 +145,7 @@ contract Stake is IStake {
|
||||||
StakingPosition storage lastPos = positions[positionsToSnatch[index]];
|
StakingPosition storage lastPos = positions[positionsToSnatch[index]];
|
||||||
if (lastPos.creationTime == 0) {
|
if (lastPos.creationTime == 0) {
|
||||||
//TODO:
|
//TODO:
|
||||||
revert PositionNotFound();
|
revert PositionNotFound(positionsToSnatch[index], receiver);
|
||||||
}
|
}
|
||||||
// check that tax lower
|
// check that tax lower
|
||||||
if (taxRate <= lastPos.taxRate) {
|
if (taxRate <= lastPos.taxRate) {
|
||||||
|
|
@ -199,72 +193,76 @@ contract Stake is IStake {
|
||||||
emit PositionCreated(positionId, sp.owner, sp.share, sp.creationTime, sp.taxRate);
|
emit PositionCreated(positionId, sp.owner, sp.share, sp.creationTime, sp.taxRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeTax(uint256 positionID, uint32 taxRate) public {
|
function changeTax(uint256 positionId, uint32 taxRate) public {
|
||||||
require(taxRate < TAX_RATES.length, "tax rate out of bounds");
|
require(taxRate < TAX_RATES.length, "tax rate out of bounds");
|
||||||
StakingPosition storage pos = positions[positionID];
|
StakingPosition storage pos = positions[positionId];
|
||||||
|
if (pos.creationTime == 0) {
|
||||||
|
revert PositionNotFound(positionId, msg.sender);
|
||||||
|
}
|
||||||
if (pos.owner != msg.sender) {
|
if (pos.owner != msg.sender) {
|
||||||
revert NoPermission(msg.sender, pos.owner);
|
revert NoPermission(msg.sender, pos.owner);
|
||||||
}
|
}
|
||||||
//TODO: implement not found
|
|
||||||
// to prevent snatch-and-change grieving attack, pay TAX_FLOOR_DURATION
|
// to prevent snatch-and-change grieving attack, pay TAX_FLOOR_DURATION
|
||||||
require(taxRate > pos.taxRate, "tax too low to snatch");
|
require(taxRate > pos.taxRate, "tax too low to snatch");
|
||||||
_payTax(positionID, pos, TAX_FLOOR_DURATION);
|
_payTax(positionId, pos, TAX_FLOOR_DURATION);
|
||||||
pos.taxRate = taxRate;
|
pos.taxRate = taxRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitPosition(uint256 positionId) public {
|
function exitPosition(uint256 positionId) public {
|
||||||
StakingPosition storage pos = positions[positionId];
|
StakingPosition storage pos = positions[positionId];
|
||||||
|
if (pos.creationTime == 0) {
|
||||||
|
revert PositionNotFound(positionId, msg.sender);
|
||||||
|
}
|
||||||
if (pos.owner != msg.sender) {
|
if (pos.owner != msg.sender) {
|
||||||
revert NoPermission(msg.sender, pos.owner);
|
revert NoPermission(msg.sender, pos.owner);
|
||||||
}
|
}
|
||||||
//TODO: implement not found
|
|
||||||
// to prevent snatch-and-exit grieving attack, pay TAX_FLOOR_DURATION
|
// to prevent snatch-and-exit grieving attack, pay TAX_FLOOR_DURATION
|
||||||
_payTax(positionId, pos, TAX_FLOOR_DURATION);
|
_payTax(positionId, pos, TAX_FLOOR_DURATION);
|
||||||
_exitPosition(positionId, pos);
|
_exitPosition(positionId, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: write a bot that calls this function regularly
|
// TODO: write a bot that calls this function regularly
|
||||||
function payTax(uint256 positionID) public {
|
function payTax(uint256 positionId) public {
|
||||||
StakingPosition storage pos = positions[positionID];
|
StakingPosition storage pos = positions[positionId];
|
||||||
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
||||||
_payTax(positionID, pos, 0);
|
_payTax(positionId, pos, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function taxDue(uint256 positionID, uint256 taxFloorDuration) public view returns (uint256 amountDue) {
|
function taxDue(uint256 positionId, uint256 taxFloorDuration) public view returns (uint256 amountDue) {
|
||||||
StakingPosition storage pos = positions[positionID];
|
StakingPosition storage pos = positions[positionId];
|
||||||
// ihet = Implied Holding Expiry Timestamp
|
// ihet = Implied Holding Expiry Timestamp
|
||||||
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
||||||
? pos.creationTime + taxFloorDuration
|
? pos.creationTime + taxFloorDuration
|
||||||
: block.timestamp;
|
: block.timestamp;
|
||||||
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
||||||
uint256 assetsBefore = sharesToAssets(pos.share, Math.Rounding.Down);
|
uint256 assetsBefore = sharesToAssets(pos.share);
|
||||||
amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _payTax(uint256 positionID, StakingPosition storage pos, uint256 taxFloorDuration) private {
|
function _payTax(uint256 positionId, StakingPosition storage pos, uint256 taxFloorDuration) private {
|
||||||
// ihet = Implied Holding Expiry Timestamp
|
// ihet = Implied Holding Expiry Timestamp
|
||||||
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
||||||
? pos.creationTime + taxFloorDuration
|
? pos.creationTime + taxFloorDuration
|
||||||
: block.timestamp;
|
: block.timestamp;
|
||||||
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
||||||
uint256 assetsBefore = sharesToAssets(pos.share, Math.Rounding.Down);
|
uint256 assetsBefore = sharesToAssets(pos.share);
|
||||||
uint256 taxAmountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
uint256 taxAmountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||||
if (taxAmountDue >= assetsBefore) {
|
if (taxAmountDue >= assetsBefore) {
|
||||||
// can not pay more tax than value of position
|
// can not pay more tax than value of position
|
||||||
taxAmountDue = assetsBefore;
|
taxAmountDue = assetsBefore;
|
||||||
}
|
}
|
||||||
SafeERC20.safeTransfer(harb, taxPool, taxAmountDue);
|
SafeERC20.safeTransfer(harb, taxPool, taxAmountDue);
|
||||||
emit TaxPaid(positionID, pos.owner, taxAmountDue);
|
emit TaxPaid(positionId, pos.owner, taxAmountDue);
|
||||||
if (assetsBefore - taxAmountDue > 0) {
|
if (assetsBefore - taxAmountDue > 0) {
|
||||||
// if something left over, update storage
|
// if something left over, update storage
|
||||||
uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue, Math.Rounding.Down);
|
uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue);
|
||||||
outstandingStake -= pos.share - shareAfterTax;
|
outstandingStake -= pos.share - shareAfterTax;
|
||||||
pos.share = shareAfterTax;
|
pos.share = shareAfterTax;
|
||||||
pos.lastTaxTime = uint32(block.timestamp);
|
pos.lastTaxTime = uint32(block.timestamp);
|
||||||
} else {
|
} else {
|
||||||
// if nothing left over, liquidate position
|
// if nothing left over, liquidate position
|
||||||
outstandingStake -= pos.share;
|
outstandingStake -= pos.share;
|
||||||
emit PositionRemoved(positionID, pos.share, pos.lastTaxTime);
|
emit PositionRemoved(positionId, pos.share, pos.lastTaxTime);
|
||||||
delete pos.owner;
|
delete pos.owner;
|
||||||
delete pos.creationTime;
|
delete pos.creationTime;
|
||||||
}
|
}
|
||||||
|
|
@ -273,7 +271,7 @@ contract Stake is IStake {
|
||||||
function _exitPosition(uint256 positionId, StakingPosition storage pos) private {
|
function _exitPosition(uint256 positionId, StakingPosition storage pos) private {
|
||||||
outstandingStake -= pos.share;
|
outstandingStake -= pos.share;
|
||||||
address owner = pos.owner;
|
address owner = pos.owner;
|
||||||
uint256 assets = sharesToAssets(pos.share, Math.Rounding.Down);
|
uint256 assets = sharesToAssets(pos.share);
|
||||||
emit PositionRemoved(positionId, pos.share, pos.lastTaxTime);
|
emit PositionRemoved(positionId, pos.share, pos.lastTaxTime);
|
||||||
delete pos.owner;
|
delete pos.owner;
|
||||||
delete pos.creationTime;
|
delete pos.creationTime;
|
||||||
|
|
@ -282,7 +280,7 @@ contract Stake is IStake {
|
||||||
|
|
||||||
function _shrinkPosition(uint256 positionId, StakingPosition storage pos, uint256 sharesToTake) private {
|
function _shrinkPosition(uint256 positionId, StakingPosition storage pos, uint256 sharesToTake) private {
|
||||||
require (sharesToTake < pos.share, "position too small");
|
require (sharesToTake < pos.share, "position too small");
|
||||||
uint256 assets = sharesToAssets(sharesToTake, Math.Rounding.Down);
|
uint256 assets = sharesToAssets(sharesToTake);
|
||||||
pos.share -= sharesToTake;
|
pos.share -= sharesToTake;
|
||||||
emit PositionShrunk(positionId, pos.share, pos.lastTaxTime, sharesToTake);
|
emit PositionShrunk(positionId, pos.share, pos.lastTaxTime, sharesToTake);
|
||||||
SafeERC20.safeTransfer(harb, pos.owner, assets);
|
SafeERC20.safeTransfer(harb, pos.owner, assets);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
pragma solidity ^0.8.13;
|
|
||||||
|
|
||||||
interface IStake {
|
|
||||||
function dormantSupply() external view returns (uint256);
|
|
||||||
}
|
|
||||||
|
|
@ -72,7 +72,7 @@ contract BaseLineLPTest is PoolSerializer {
|
||||||
weth = IWETH9(address(new WETH()));
|
weth = IWETH9(address(new WETH()));
|
||||||
|
|
||||||
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
||||||
harb = new Harb("HARB", "HARB", factoryAddress, address(weth), tc);
|
harb = new Harb("HARB", "HARB", tc);
|
||||||
|
|
||||||
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harb), FEE));
|
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harb), FEE));
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue