first deployment
This commit is contained in:
parent
9279e0c045
commit
06581a0b8d
13 changed files with 180 additions and 225 deletions
3
onchain/.gitignore
vendored
3
onchain/.gitignore
vendored
|
|
@ -12,3 +12,6 @@ docs/
|
|||
|
||||
# Dotenv file
|
||||
.env
|
||||
.secret
|
||||
|
||||
/broadcast/
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,4 @@
|
|||
@openzeppelin/=lib/openzeppelin-contracts/contracts/
|
||||
@uniswap-v3-core/=lib/uni-v3-lib/node_modules/@uniswap/v3-core/contracts/
|
||||
@uniswap-v3-periphery=lib/uni-v3-lib/node_modules/@uniswap/v3-periphery/contracts/
|
||||
@aperture/uni-v3-lib/=lib/uni-v3-lib/src/
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {Script, console} from "forge-std/Script.sol";
|
||||
|
||||
contract CounterScript is Script {
|
||||
function setUp() public {}
|
||||
|
||||
function run() public {
|
||||
vm.broadcast();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
pragma solidity ^0.8.4;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import { TwabController } from "pt-v5-twab-controller/TwabController.sol";
|
||||
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||
import "../src/Harb.sol";
|
||||
import "../src/Stake.sol";
|
||||
|
||||
contract GoerliScript is Script {
|
||||
contract SepoliaScript is Script {
|
||||
function setUp() public {}
|
||||
|
||||
function run() public {
|
||||
|
|
@ -13,11 +13,11 @@ contract GoerliScript is Script {
|
|||
uint256 privateKey = vm.deriveKey(seedPhrase, 0);
|
||||
vm.startBroadcast(privateKey);
|
||||
|
||||
TwabController tc = new TwabController(60*60*24, uint32(block.timestamp));
|
||||
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));
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import { ERC20, IERC20, IERC20Metadata } from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import { ERC20Permit, IERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.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 {ERC20, IERC20, IERC20Metadata} from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import {ERC20Permit, IERC20Permit} from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol";
|
||||
import {IStake} from "./interfaces/IStake.sol";
|
||||
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||
|
||||
/**
|
||||
* @title TWAB ERC20 Token
|
||||
|
|
@ -16,7 +16,6 @@ import { TwabController } from "pt-v5-twab-controller/TwabController.sol";
|
|||
* gas savings. Any mints that increase a balance past this limit will fail.
|
||||
*/
|
||||
contract Harb is ERC20, ERC20Permit {
|
||||
|
||||
address public constant TAX_POOL = address(2);
|
||||
|
||||
/* ============ Public Variables ============ */
|
||||
|
|
@ -47,11 +46,10 @@ contract Harb is ERC20, ERC20Permit {
|
|||
* @param name_ The name of the token
|
||||
* @param symbol_ The token symbol
|
||||
*/
|
||||
constructor(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
TwabController twabController_
|
||||
) ERC20(name_, symbol_) ERC20Permit(name_) {
|
||||
constructor(string memory name_, string memory symbol_, TwabController twabController_)
|
||||
ERC20(name_, symbol_)
|
||||
ERC20Permit(name_)
|
||||
{
|
||||
if (address(0) == address(twabController_)) revert ZeroAddressInConstructor();
|
||||
twabController = twabController_;
|
||||
}
|
||||
|
|
@ -87,9 +85,7 @@ contract Harb is ERC20, ERC20Permit {
|
|||
/* ============ Public ERC20 Overrides ============ */
|
||||
|
||||
/// @inheritdoc ERC20
|
||||
function balanceOf(
|
||||
address _account
|
||||
) public view virtual override(ERC20) returns (uint256) {
|
||||
function balanceOf(address _account) public view virtual override(ERC20) returns (uint256) {
|
||||
return twabController.balanceOf(address(this), _account);
|
||||
}
|
||||
|
||||
|
|
@ -98,8 +94,6 @@ contract Harb is ERC20, ERC20Permit {
|
|||
return twabController.totalSupply(address(this));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ============ Internal ERC20 Overrides ============ */
|
||||
|
||||
/**
|
||||
|
|
@ -115,8 +109,8 @@ contract Harb is ERC20, ERC20Permit {
|
|||
uint256 activeSupply = totalSupply() - stakingPoolBalance;
|
||||
uint256 dormantStake = IStake(stakingPool).dormantSupply();
|
||||
if (stakingPoolBalance > 0) {
|
||||
uint256 newStake = stakingPoolBalance * amount / (activeSupply + dormantStake);
|
||||
_mint(stakingPool, newStake);
|
||||
uint256 newStake = stakingPoolBalance * amount / (activeSupply + dormantStake);
|
||||
_mint(stakingPool, newStake);
|
||||
}
|
||||
|
||||
twabController.mint(receiver, SafeCast.toUint96(amount));
|
||||
|
|
@ -151,5 +145,4 @@ contract Harb is ERC20, ERC20Permit {
|
|||
twabController.transfer(_from, _to, SafeCast.toUint96(_amount));
|
||||
emit Transfer(_from, _to, _amount);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import './lib/PositionKey.sol';
|
||||
import './lib/FixedPoint128.sol';
|
||||
import '@aperture/uni-v3-lib/TickMath.sol';
|
||||
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/token/ERC20/IERC20.sol';
|
||||
import "@uniswap-v3-periphery/libraries/PositionKey.sol";
|
||||
import "@uniswap-v3-core/libraries/FixedPoint128.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import "@aperture/uni-v3-lib/PoolAddress.sol";
|
||||
import "@aperture/uni-v3-lib/CallbackValidation.sol";
|
||||
import "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
|
||||
/**
|
||||
* @title LiquidityManager - A contract that supports the $bloodX ecosystem. It
|
||||
* protects the communities liquidity while allowing a manager role to
|
||||
* @title LiquidityManager - A contract that supports the $bloodX ecosystem. It
|
||||
* protects the communities liquidity while allowing a manager role to
|
||||
* take strategic liqudity positions.
|
||||
*/
|
||||
contract LiquidityManager {
|
||||
|
||||
// default fee of 1%
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
|
||||
|
|
@ -63,7 +62,7 @@ contract LiquidityManager {
|
|||
mapping(bytes26 => TokenPosition) private _positions;
|
||||
|
||||
modifier checkDeadline(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, 'Transaction too old');
|
||||
require(block.timestamp <= deadline, "Transaction too old");
|
||||
_;
|
||||
}
|
||||
|
||||
|
|
@ -78,13 +77,9 @@ contract LiquidityManager {
|
|||
/// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
|
||||
event DecreaseLiquidity(address indexed token, uint128 liquidity, uint256 amount0, uint256 amount1);
|
||||
|
||||
constructor(
|
||||
address _factory,
|
||||
address _WETH9
|
||||
) {
|
||||
constructor(address _factory, address _WETH9) {
|
||||
factory = _factory;
|
||||
WETH9 = _WETH9;
|
||||
|
||||
}
|
||||
|
||||
function getToken(address token0, address token1) internal view returns (bool token0isWeth, address token) {
|
||||
|
|
@ -112,26 +107,16 @@ contract LiquidityManager {
|
|||
}
|
||||
}
|
||||
|
||||
function positions(address token, int24 tickLower, int24 tickUpper) external view
|
||||
returns (
|
||||
uint128 liquidity,
|
||||
uint256 feeGrowthInside0LastX128,
|
||||
uint256 feeGrowthInside1LastX128
|
||||
)
|
||||
function positions(address token, int24 tickLower, int24 tickUpper)
|
||||
external
|
||||
view
|
||||
returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
|
||||
{
|
||||
TokenPosition memory position = _positions[posKey(token, tickLower, tickUpper)];
|
||||
return (
|
||||
position.liquidity,
|
||||
position.feeGrowthInside0LastX128,
|
||||
position.feeGrowthInside1LastX128
|
||||
);
|
||||
return (position.liquidity, position.feeGrowthInside0LastX128, position.feeGrowthInside1LastX128);
|
||||
}
|
||||
|
||||
function uniswapV3MintCallback(
|
||||
uint256 amount0Owed,
|
||||
uint256 amount1Owed,
|
||||
bytes calldata data
|
||||
) external {
|
||||
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata data) external {
|
||||
PoolKey memory poolKey = abi.decode(data, (PoolKey));
|
||||
CallbackValidation.verifyCallback(factory, poolKey);
|
||||
|
||||
|
|
@ -147,62 +132,57 @@ contract LiquidityManager {
|
|||
// compute the liquidity amount
|
||||
uint128 liquidity;
|
||||
{
|
||||
(uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
|
||||
(uint160 sqrtPriceX96,,,,,,) = pool.slot0();
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper);
|
||||
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmounts(
|
||||
sqrtPriceX96,
|
||||
sqrtRatioAX96,
|
||||
sqrtRatioBX96,
|
||||
params.amount0Desired,
|
||||
params.amount1Desired
|
||||
sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, params.amount0Desired, params.amount1Desired
|
||||
);
|
||||
}
|
||||
|
||||
(bool token0isWeth, address token) = getToken(params.token0, params.token1);
|
||||
{
|
||||
(uint256 amount0, uint256 amount1) = pool.mint(
|
||||
address(this),
|
||||
params.tickLower,
|
||||
params.tickUpper,
|
||||
liquidity,
|
||||
abi.encode(poolKey)
|
||||
);
|
||||
(uint256 amount0, uint256 amount1) =
|
||||
pool.mint(address(this), params.tickLower, params.tickUpper, liquidity, abi.encode(poolKey));
|
||||
|
||||
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');
|
||||
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, "Price slippage check");
|
||||
emit IncreaseLiquidity(token, liquidity, amount0, amount1);
|
||||
}
|
||||
|
||||
bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
|
||||
|
||||
// this is now updated to the current transaction
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
|
||||
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
|
||||
TokenPosition memory position = _positions[posKey(token, params.tickLower, params.tickUpper)];
|
||||
if (liquidity == 0) {
|
||||
// create entry
|
||||
position = TokenPosition({
|
||||
position = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
} else {
|
||||
// update entry
|
||||
updateFeesOwed(token0isWeth, token, uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
|
||||
position.liquidity,
|
||||
FixedPoint128.Q128
|
||||
updateFeesOwed(
|
||||
token0isWeth,
|
||||
token,
|
||||
uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
|
||||
position.liquidity,
|
||||
FixedPoint128.Q128
|
||||
)
|
||||
),
|
||||
uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
|
||||
position.liquidity,
|
||||
FixedPoint128.Q128
|
||||
)
|
||||
)
|
||||
), uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
|
||||
position.liquidity,
|
||||
FixedPoint128.Q128
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
|
||||
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
|
||||
|
|
@ -227,27 +207,28 @@ contract LiquidityManager {
|
|||
|
||||
(amount0, amount1) = pool.burn(params.tickLower, params.tickUpper, params.liquidity);
|
||||
|
||||
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');
|
||||
require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, "Price slippage check");
|
||||
|
||||
bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
|
||||
// this is now updated to the current transaction
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
|
||||
updateFeesOwed(token0isWeth, token, uint128(amount0) +
|
||||
uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
|
||||
positionLiquidity,
|
||||
FixedPoint128.Q128
|
||||
updateFeesOwed(
|
||||
token0isWeth,
|
||||
token,
|
||||
uint128(amount0)
|
||||
+ uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128, positionLiquidity, FixedPoint128.Q128
|
||||
)
|
||||
),
|
||||
uint128(amount1)
|
||||
+ uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128, positionLiquidity, FixedPoint128.Q128
|
||||
)
|
||||
)
|
||||
), uint128(amount1) +
|
||||
uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
|
||||
positionLiquidity,
|
||||
FixedPoint128.Q128
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
|
||||
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
|
||||
|
|
@ -257,36 +238,35 @@ contract LiquidityManager {
|
|||
emit DecreaseLiquidity(token, params.liquidity, amount0, amount1);
|
||||
}
|
||||
|
||||
|
||||
// function compareTokenToEthBalance(uint256 ethAmountInPosition, uint256 tokenAmountInPosition) external view returns (bool hasMoreToken) {
|
||||
// // Fetch the current sqrtPriceX96 from the pool
|
||||
// (uint160 sqrtPriceX96,,,) = uniswapV3Pool.slot0();
|
||||
|
||||
|
||||
// // Convert sqrtPriceX96 to a conventional price format
|
||||
// // Note: The price is calculated as (sqrtPriceX96^2 / 2^192), simplified here as (price / 2^96) for the sake of example
|
||||
// uint256 price = uint256(sqrtPriceX96) * uint256(sqrtPriceX96) / (1 << 96);
|
||||
|
||||
|
||||
// // Calculate the equivalent token amount for the ETH in the position at the current price
|
||||
// // Assuming price is expressed as the amount of token per ETH
|
||||
// uint256 equivalentTokenAmountForEth = ethAmountInPosition * price;
|
||||
|
||||
|
||||
// // Compare to the actual token amount in the position
|
||||
// hasMoreToken = tokenAmountInPosition > equivalentTokenAmountForEth;
|
||||
|
||||
|
||||
// return hasMoreToken;
|
||||
// }
|
||||
|
||||
////////
|
||||
// - check if tick in range, otherwise revert
|
||||
// - check if the position has more Token or more ETH, at current price
|
||||
// - if more ETH,
|
||||
// - calculate the amount of Token needed to be minted to bring the position to 50/50
|
||||
// - mint
|
||||
// - deposit Token into pool
|
||||
// - if more TOKEN
|
||||
// - calculate the amount of token needed to be withdrawn from the position, to bring the position to 50/50
|
||||
// - withdraw
|
||||
// - burn tokens
|
||||
////////
|
||||
// - check if tick in range, otherwise revert
|
||||
// - check if the position has more Token or more ETH, at current price
|
||||
// - if more ETH,
|
||||
// - calculate the amount of Token needed to be minted to bring the position to 50/50
|
||||
// - mint
|
||||
// - deposit Token into pool
|
||||
// - if more TOKEN
|
||||
// - calculate the amount of token needed to be withdrawn from the position, to bring the position to 50/50
|
||||
// - withdraw
|
||||
// - burn tokens
|
||||
|
||||
// function rebalance(address token, int24 tickLower, int24 tickUpper) external {
|
||||
// bool ETH_TOKEN_ZERO = WETH9 < token;
|
||||
|
|
@ -335,9 +315,5 @@ contract LiquidityManager {
|
|||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import { IERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import {IERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.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 "./interfaces/IStake.sol";
|
||||
import "./Harb.sol";
|
||||
|
|
@ -13,26 +13,26 @@ contract Stake is IStake {
|
|||
using Math for uint256;
|
||||
|
||||
uint256 internal DECIMAL_OFFSET = 5 + 2;
|
||||
uint256 internal constant MAX_STAKE = 20; // 20% of HARB supply
|
||||
uint256 internal constant MAX_TAX = 1000; // max 1000% tax per year
|
||||
uint256 internal constant MAX_STAKE = 20; // 20% of HARB supply
|
||||
uint256 internal constant MAX_TAX = 1000; // max 1000% tax per year
|
||||
uint256 internal constant TAX_RATE_BASE = 100;
|
||||
uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3; //this duration is the minimum basis for fee calculation, regardless of actual holding time.
|
||||
/**
|
||||
* @dev Attempted to deposit more assets than the max amount for `receiver`.
|
||||
*/
|
||||
|
||||
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
||||
error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
|
||||
error SharesTooLow(address receiver, uint256 assets, uint256 sharesWanted, uint256 minStake);
|
||||
error NoPermission(address requester, address owner);
|
||||
error PositionNotFound();
|
||||
|
||||
|
||||
struct StakingPosition {
|
||||
uint256 share;
|
||||
address owner;
|
||||
uint32 creationTime;
|
||||
uint32 lastTaxTime;
|
||||
uint32 taxRate; // e.g. value of 60 = 60% tax per year
|
||||
uint32 taxRate; // e.g. value of 60 = 60% tax per year
|
||||
}
|
||||
|
||||
uint256 public immutable totalSupply;
|
||||
|
|
@ -41,23 +41,20 @@ contract Stake is IStake {
|
|||
uint256 public outstandingStake;
|
||||
uint256 private lastTokenId;
|
||||
uint256 public minStake;
|
||||
mapping (uint256 positionID => StakingPosition) public positions;
|
||||
mapping(uint256 positionID => StakingPosition) public positions;
|
||||
|
||||
|
||||
constructor(
|
||||
address _tokenContract
|
||||
) {
|
||||
constructor(address _tokenContract) {
|
||||
tokenContract = IERC20Metadata(_tokenContract);
|
||||
|
||||
totalSupply = 10**(tokenContract.decimals() + DECIMAL_OFFSET);
|
||||
|
||||
totalSupply = 10 ** (tokenContract.decimals() + DECIMAL_OFFSET);
|
||||
taxPool = Harb(_tokenContract).TAX_POOL();
|
||||
}
|
||||
|
||||
function dormantSupply() public view override returns(uint256) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -71,16 +68,17 @@ contract Stake is IStake {
|
|||
return shares.mulDiv(tokenContract.totalSupply(), totalSupply, rounding);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* TODO: deal with metatransactions: While these are generally available
|
||||
/**
|
||||
* TODO: deal with metatransactions: While these are generally available
|
||||
* via msg.sender and msg.data, they should not be accessed in such a direct
|
||||
* manner, since when dealing with meta-transactions the account sending and
|
||||
* paying for execution may not be the actual sender (as far as an application
|
||||
* is concerned).
|
||||
*/
|
||||
function snatch(uint256 assets, address receiver, uint32 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, Math.Rounding.Down);
|
||||
if (sharesWanted < minStake) {
|
||||
|
|
@ -88,10 +86,10 @@ contract Stake is IStake {
|
|||
}
|
||||
|
||||
// run through all suggested positions to snatch
|
||||
for (uint i = 0; i < positionsToSnatch.length; i++) {
|
||||
for (uint256 i = 0; i < positionsToSnatch.length; i++) {
|
||||
StakingPosition storage pos = positions[positionsToSnatch[i]];
|
||||
if (pos.creationTime == 0) {
|
||||
//TODO:
|
||||
//TODO:
|
||||
revert PositionNotFound();
|
||||
}
|
||||
// check that tax lower
|
||||
|
|
@ -103,7 +101,7 @@ contract Stake is IStake {
|
|||
_payTax(pos, 0);
|
||||
_exitPosition(pos);
|
||||
}
|
||||
|
||||
|
||||
// try to make a new position in the free space and hope it is big enough
|
||||
uint256 availableStake = authorizedStake() - outstandingStake;
|
||||
if (sharesWanted > availableStake) {
|
||||
|
|
@ -126,17 +124,16 @@ contract Stake is IStake {
|
|||
return lastTokenId;
|
||||
}
|
||||
|
||||
|
||||
function exitPosition(uint256 positionID) public {
|
||||
StakingPosition storage pos = positions[positionID];
|
||||
if(pos.owner != msg.sender) {
|
||||
if (pos.owner != msg.sender) {
|
||||
revert NoPermission(msg.sender, pos.owner);
|
||||
}
|
||||
// to prevent snatch-and-exit grieving attack, pay TAX_FLOOR_DURATION
|
||||
_payTax(pos, TAX_FLOOR_DURATION);
|
||||
_exitPosition(pos);
|
||||
_exitPosition(pos);
|
||||
}
|
||||
|
||||
|
||||
function payTax(uint256 positionID) public {
|
||||
StakingPosition storage pos = positions[positionID];
|
||||
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
||||
|
|
@ -146,16 +143,19 @@ contract Stake is IStake {
|
|||
function taxDue(uint256 positionID, uint256 taxFloorDuration) public view returns (uint256 amountDue) {
|
||||
StakingPosition storage pos = positions[positionID];
|
||||
// ihet = Implied Holding Expiry Timestamp
|
||||
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp;
|
||||
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
||||
? pos.creationTime + taxFloorDuration
|
||||
: block.timestamp;
|
||||
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
||||
uint256 assetsBefore = sharesToAssets(pos.share, Math.Rounding.Down);
|
||||
amountDue = assetsBefore * pos.taxRate * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||
}
|
||||
|
||||
|
||||
function _payTax(StakingPosition storage pos, uint256 taxFloorDuration) private {
|
||||
// ihet = Implied Holding Expiry Timestamp
|
||||
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration) ? pos.creationTime + taxFloorDuration : block.timestamp;
|
||||
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
||||
? pos.creationTime + taxFloorDuration
|
||||
: block.timestamp;
|
||||
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
||||
uint256 assetsBefore = sharesToAssets(pos.share, Math.Rounding.Down);
|
||||
uint256 taxAmountDue = assetsBefore * pos.taxRate * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||
|
|
@ -185,5 +185,4 @@ contract Stake is IStake {
|
|||
delete pos.creationTime;
|
||||
SafeERC20.safeTransfer(tokenContract, owner, assets);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
interface IStake {
|
||||
|
||||
function dormantSupply() external view returns(uint256);
|
||||
|
||||
interface IStake {
|
||||
function dormantSupply() external view returns (uint256);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
/// @title FixedPoint128
|
||||
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
|
||||
library FixedPoint128 {
|
||||
uint256 internal constant Q128 = 0x100000000000000000000000000000000;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
/// @title FixedPoint96
|
||||
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
|
||||
/// @dev Used in SqrtPriceMath.sol
|
||||
library FixedPoint96 {
|
||||
uint8 internal constant RESOLUTION = 96;
|
||||
uint256 internal constant Q96 = 0x1000000000000000000000000;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
pragma solidity >=0.5.0;
|
||||
|
||||
library PositionKey {
|
||||
/// @dev Returns the key of the position in the core library
|
||||
function compute(
|
||||
address owner,
|
||||
int24 tickLower,
|
||||
int24 tickUpper
|
||||
) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked(owner, tickLower, tickUpper));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +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 {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||
import "../src/Harb.sol";
|
||||
import "../src/Stake.sol";
|
||||
|
||||
|
|
@ -12,8 +12,7 @@ contract HarbTest is Test {
|
|||
Stake public stake;
|
||||
|
||||
function setUp() public {
|
||||
|
||||
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", tc);
|
||||
stake = new Stake(address(harb));
|
||||
|
|
@ -21,15 +20,15 @@ contract HarbTest is Test {
|
|||
}
|
||||
|
||||
function test_MintStakeUnstake(address account, uint256 amount) public {
|
||||
vm.assume(amount > 10000);
|
||||
vm.assume(amount < 2**93); // TWAB limit = 2**96
|
||||
vm.assume(account != address(0));
|
||||
vm.assume(account != address(1)); // TWAB sponsorship address
|
||||
vm.assume(account != address(2)); // tax pool address
|
||||
vm.assume(account != address(harb));
|
||||
vm.assume(account != address(stake));
|
||||
vm.assume(amount > 10000);
|
||||
vm.assume(amount < 2 ** 93); // TWAB limit = 2**96
|
||||
vm.assume(account != address(0));
|
||||
vm.assume(account != address(1)); // TWAB sponsorship address
|
||||
vm.assume(account != address(2)); // tax pool address
|
||||
vm.assume(account != address(harb));
|
||||
vm.assume(account != address(stake));
|
||||
|
||||
// test mint
|
||||
// test mint
|
||||
uint256 totalSupplyBefore = harb.totalSupply();
|
||||
uint256 balanceBefore = harb.balanceOf(account);
|
||||
harb.setLiquidityManager(account);
|
||||
|
|
@ -47,17 +46,17 @@ contract HarbTest is Test {
|
|||
vm.prank(account);
|
||||
harb.approve(address(stake), amount);
|
||||
uint256[] memory empty;
|
||||
vm.prank(account);
|
||||
stake.snatch(amount, account, 1, empty);
|
||||
assertEq(harb.totalSupply(), totalAfter * 5, "total supply should match after stake");
|
||||
assertEq(harb.balanceOf(account), amount * 4, "balance should match after stake");
|
||||
vm.prank(account);
|
||||
stake.snatch(amount, account, 1, empty);
|
||||
assertEq(harb.totalSupply(), totalAfter * 5, "total supply should match after stake");
|
||||
assertEq(harb.balanceOf(account), amount * 4, "balance should match after stake");
|
||||
assertEq(harb.balanceOf(address(stake)), amount, "balance should match after stake");
|
||||
(uint256 share, address owner, uint32 creationTime, uint32 lastTaxTime, uint32 taxRate) = stake.positions(0);
|
||||
assertEq(share, stake.totalSupply() / 5, "share should match");
|
||||
assertEq(share, stake.totalSupply() / 5, "share should match");
|
||||
assertEq(owner, account, "owners should match");
|
||||
assertEq(taxRate, 1, "tax rate should match");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test unstake
|
||||
{
|
||||
uint256 timeBefore = block.timestamp;
|
||||
|
|
@ -65,10 +64,9 @@ contract HarbTest is Test {
|
|||
uint256 taxDue = stake.taxDue(0, 60 * 60 * 24 * 3);
|
||||
console.logUint(taxDue);
|
||||
console.log("tax due :%i", taxDue);
|
||||
vm.prank(account);
|
||||
stake.exitPosition(0);
|
||||
assertApproxEqRel(harb.balanceOf(account), amount * 5 - taxDue, 1e15, "balance should match");
|
||||
}
|
||||
vm.prank(account);
|
||||
stake.exitPosition(0);
|
||||
assertApproxEqRel(harb.balanceOf(account), amount * 5 - taxDue, 1e14, "balance should match");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue