new deployment with public position counter
This commit is contained in:
parent
4e895f57e9
commit
2314ff51c1
8 changed files with 236 additions and 328 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1,13 +1,15 @@
|
|||
pragma solidity ^0.8.4;
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "../src/Harb.sol";
|
||||
import "../src/Stake.sol";
|
||||
import {BaseLineLP} from "../src/BaseLineLP.sol";
|
||||
|
||||
address constant WETH = 0xb16F35c0Ae2912430DAc15764477E179D9B9EbEa; //Sepolia
|
||||
address constant V3_FACTORY = 0x0227628f3F023bb0B980b67D528571c95c6DaC1c; //Sepolia
|
||||
address constant TWABC = 0x79Deda2d38A3232fE9b68DcB7d6b461d793A7236; //new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
|
||||
contract SepoliaScript is Script {
|
||||
|
|
@ -18,14 +20,14 @@ contract SepoliaScript is Script {
|
|||
uint256 privateKey = vm.deriveKey(seedPhrase, 0);
|
||||
vm.startBroadcast(privateKey);
|
||||
|
||||
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
||||
TwabController tc = TwabController(TWABC);
|
||||
Harb harb = new Harb("Harberger Tax", "HARB", V3_FACTORY, WETH, tc);
|
||||
Stake stake = new Stake(address(harb));
|
||||
harb.setStakingPool(address(stake));
|
||||
IUniswapV3Factory factory = IUniswapV3Factory(V3_FACTORY);
|
||||
factory.createPool(WETH, address(harb), FEE);
|
||||
LiquidityManager liquidityManager = new LiquidityManager(V3_FACTORY, WETH, address(harb));
|
||||
harb.setLiquidityManager(address(liquidityManager));
|
||||
// BaseLineLP liquidityManager = new BaseLineLP(V3_FACTORY, WETH, address(harb));
|
||||
// harb.setLiquidityManager(address(liquidityManager));
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
195
onchain/src/BaseLineLP.sol
Normal file
195
onchain/src/BaseLineLP.sol
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
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";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import {Harb} from "./Harb.sol";
|
||||
|
||||
/**
|
||||
* @title LiquidityManager - A contract that supports the harb ecosystem. It
|
||||
* protects the communities liquidity while allowing a manager role to
|
||||
* take strategic liqudity positions.
|
||||
*/
|
||||
contract BaseLineLP {
|
||||
// default fee of 1%
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
|
||||
// the address of the Uniswap V3 factory
|
||||
address immutable factory;
|
||||
IWETH9 immutable weth;
|
||||
Harb immutable harb;
|
||||
IUniswapV3Pool immutable pool;
|
||||
PoolKey private poolKey;
|
||||
bool immutable token0isWeth;
|
||||
|
||||
|
||||
struct TokenPosition {
|
||||
// the liquidity of the position
|
||||
uint128 liquidity;
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
// the fee growth of the aggregate position as of the last action on the individual position
|
||||
uint256 feeGrowthInside0LastX128;
|
||||
uint256 feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
TokenPosition public floor;
|
||||
TokenPosition public anchor;
|
||||
TokenPosition public discovery;
|
||||
|
||||
modifier checkDeadline(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, "Transaction too old");
|
||||
_;
|
||||
}
|
||||
|
||||
/// @notice Emitted when liquidity is increased for a position
|
||||
/// @param liquidity The amount by which liquidity for the NFT position was increased
|
||||
/// @param amount0 The amount of token0 that was paid for the increase in liquidity
|
||||
/// @param amount1 The amount of token1 that was paid for the increase in liquidity
|
||||
event IncreaseLiquidity(int24 indexed tickLower, int24 indexed tickUpper, uint128 liquidity, uint256 amount0, uint256 amount1);
|
||||
/// @notice Emitted when liquidity is decreased for a position
|
||||
/// @param liquidity The amount by which liquidity for the NFT position was decreased
|
||||
/// @param ethReceived The amount of WETH that was accounted for the decrease in liquidity
|
||||
event PositionLiquidated(int24 indexed tickLower, int24 indexed tickUpper, uint128 liquidity, uint256 ethReceived);
|
||||
|
||||
constructor(address _factory, address _WETH9, address _harb) {
|
||||
factory = _factory;
|
||||
weth = IWETH9(_WETH9);
|
||||
poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE);
|
||||
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
|
||||
harb = Harb(_harb);
|
||||
token0isWeth = _WETH9 < _harb;
|
||||
}
|
||||
|
||||
function posKey(int24 tickLower, int24 tickUpper) internal pure returns (bytes6 _posKey) {
|
||||
bytes memory _posKeyBytes = abi.encodePacked(tickLower, tickUpper);
|
||||
assembly {
|
||||
_posKey := mload(add(_posKeyBytes, 6))
|
||||
}
|
||||
}
|
||||
|
||||
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
|
||||
CallbackValidation.verifyCallback(factory, poolKey);
|
||||
if (amount0Owed > 0) IERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
|
||||
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
|
||||
}
|
||||
|
||||
function createPosition(int24 tickLower, int24 tickUpper, uint256 amount) internal {
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
uint128 liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, amount / 20
|
||||
);
|
||||
pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
||||
// read position and start tracking in storage
|
||||
bytes32 positionKey = PositionKey.compute(address(this), tickLower, tickUpper);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
discovery = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: tickLower,
|
||||
tickUpper: tickUpper,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// called once at the beginning
|
||||
function deployLiquidity(int24 startTick, uint256 amount) external {
|
||||
require(floor.liquidity == 0, "already set up");
|
||||
require(anchor.liquidity == 0, "already set up");
|
||||
require(discovery.liquidity == 0, "already set up");
|
||||
harb.mint(amount);
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
|
||||
// create floor
|
||||
if (token0isWeth) {
|
||||
tickLower = startTick;
|
||||
tickUpper = startTick + 200;
|
||||
createPosition(tickLower, tickUpper, amount / 10);
|
||||
} else {
|
||||
tickLower = startTick - 200;
|
||||
tickUpper = startTick;
|
||||
createPosition(tickLower, tickUpper, amount / 10);
|
||||
}
|
||||
|
||||
// create anchor
|
||||
if (token0isWeth) {
|
||||
tickLower += 201;
|
||||
tickUpper += 601;
|
||||
createPosition(tickLower, tickUpper, amount / 20);
|
||||
} else {
|
||||
tickLower -= 601;
|
||||
tickUpper -= 201;
|
||||
createPosition(tickLower, tickUpper, amount / 10);
|
||||
}
|
||||
|
||||
// create discovery
|
||||
if (token0isWeth) {
|
||||
tickLower += 601;
|
||||
tickUpper += 11001;
|
||||
createPosition(tickLower, tickUpper, harb.balanceOf(address(this)));
|
||||
} else {
|
||||
tickLower -= 11001;
|
||||
tickUpper -= 601;
|
||||
createPosition(tickLower, tickUpper, harb.balanceOf(address(this)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getInverseAmountsForLiquidity(
|
||||
uint160 sqrtRatioX96,
|
||||
uint160 sqrtRatioAX96,
|
||||
uint160 sqrtRatioBX96,
|
||||
uint128 liquidity
|
||||
) internal pure returns (uint256 amount0, uint256 amount1) {
|
||||
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
|
||||
|
||||
if (sqrtRatioX96 <= sqrtRatioAX96) {
|
||||
amount1 = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
|
||||
} else if (sqrtRatioX96 < sqrtRatioBX96) {
|
||||
amount1 = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
|
||||
amount0 = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
|
||||
} else {
|
||||
amount0 = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateCapacity() public view returns (uint256 capacity) {
|
||||
capacity = 0;
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(currentTick);
|
||||
// handle floor
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floor.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floor.tickUpper);
|
||||
(uint256 amount0, uint256 amount1) = getInverseAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, floor.liquidity);
|
||||
capacity += token0isWeth ? amount1 : amount0;
|
||||
}
|
||||
|
||||
// handle anchor
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(anchor.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(anchor.tickUpper);
|
||||
(uint256 amount0, uint256 amount1) = getInverseAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, anchor.liquidity);
|
||||
capacity += token0isWeth ? amount1 : amount0;
|
||||
}
|
||||
|
||||
// handle discovery
|
||||
{
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(discovery.tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(discovery.tickUpper);
|
||||
(uint256 amount0, uint256 amount1) = getInverseAmountsForLiquidity(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, discovery.liquidity);
|
||||
capacity += token0isWeth ? amount1 : amount0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ 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 {LiquidityManager} from "./LiquidityManager.sol";
|
||||
import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
|
|
|
|||
|
|
@ -1,303 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
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 harb 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);
|
||||
|
||||
// the address of the Uniswap V3 factory
|
||||
address public immutable factory;
|
||||
// the address of WETH9
|
||||
IWETH9 immutable weth;
|
||||
|
||||
Harb immutable harb;
|
||||
|
||||
IUniswapV3Pool public immutable pool;
|
||||
|
||||
bool immutable token0isWeth;
|
||||
|
||||
struct AddLiquidityParams {
|
||||
address token0;
|
||||
address token1;
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
uint256 amount0Desired;
|
||||
uint256 amount1Desired;
|
||||
uint256 amount0Min;
|
||||
uint256 amount1Min;
|
||||
uint256 deadline;
|
||||
}
|
||||
|
||||
struct DecreaseLiquidityParams {
|
||||
address token0;
|
||||
address token1;
|
||||
int24 tickLower;
|
||||
int24 tickUpper;
|
||||
uint128 liquidity;
|
||||
uint256 amount0Min;
|
||||
uint256 amount1Min;
|
||||
uint256 deadline;
|
||||
}
|
||||
|
||||
struct TokenPosition {
|
||||
// the liquidity of the position
|
||||
uint128 liquidity;
|
||||
uint128 ethOwed;
|
||||
// the fee growth of the aggregate position as of the last action on the individual position
|
||||
uint256 feeGrowthInside0LastX128;
|
||||
uint256 feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
/// @dev The token ID position data
|
||||
mapping(bytes26 => TokenPosition) private _positions;
|
||||
|
||||
modifier checkDeadline(uint256 deadline) {
|
||||
require(block.timestamp <= deadline, "Transaction too old");
|
||||
_;
|
||||
}
|
||||
|
||||
/// @notice Emitted when liquidity is increased for a position
|
||||
/// @param liquidity The amount by which liquidity for the NFT position was increased
|
||||
/// @param amount0 The amount of token0 that was paid for the increase in liquidity
|
||||
/// @param amount1 The amount of token1 that was paid for the increase in liquidity
|
||||
event IncreaseLiquidity(address indexed token, uint128 liquidity, uint256 amount0, uint256 amount1);
|
||||
/// @notice Emitted when liquidity is decreased for a position
|
||||
/// @param liquidity The amount by which liquidity for the NFT position was decreased
|
||||
/// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
|
||||
/// @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, address _harb) {
|
||||
factory = _factory;
|
||||
weth = IWETH9(_WETH9);
|
||||
PoolKey memory poolKey = PoolAddress.getPoolKey(_WETH9, _harb, FEE);
|
||||
pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
|
||||
harb = Harb(_harb);
|
||||
token0isWeth = _WETH9 < _harb;
|
||||
}
|
||||
|
||||
function updateFeesOwed(bool token0isEth, address token, uint128 tokensOwed0, uint128 tokensOwed1) internal {
|
||||
(uint128 tokensOwed, uint128 ethOwed) = getFeesOwed(token);
|
||||
tokensOwed += token0isEth ? tokensOwed1 : tokensOwed0;
|
||||
ethOwed += token0isEth ? tokensOwed0 : tokensOwed1;
|
||||
_feesOwed[token] = uint256(tokensOwed) << 128 + ethOwed;
|
||||
}
|
||||
|
||||
function posKey(int24 tickLower, int24 tickUpper) internal pure returns (bytes6 _posKey) {
|
||||
bytes memory _posKeyBytes = abi.encodePacked(tickLower, tickUpper);
|
||||
assembly {
|
||||
_posKey := mload(add(_posKeyBytes, 6))
|
||||
}
|
||||
}
|
||||
|
||||
function positions(int24 tickLower, int24 tickUpper)
|
||||
external
|
||||
view
|
||||
returns (uint128 liquidity, uint128 ethOwed, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128)
|
||||
{
|
||||
TokenPosition memory position = _positions[posKey(tickLower, tickUpper)];
|
||||
return (position.liquidity, position.ethOwed, position.feeGrowthInside0LastX128, position.feeGrowthInside1LastX128);
|
||||
}
|
||||
|
||||
function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata data) external {
|
||||
PoolKey memory poolKey = abi.decode(data, (PoolKey));
|
||||
CallbackValidation.verifyCallback(factory, poolKey);
|
||||
|
||||
if (amount0Owed > 0) IERC20(poolKey.token0).transfer(msg.sender, amount0Owed);
|
||||
if (amount1Owed > 0) IERC20(poolKey.token1).transfer(msg.sender, amount1Owed);
|
||||
}
|
||||
|
||||
/// @notice Add liquidity to an initialized pool
|
||||
function addLiquidity(AddLiquidityParams memory params) internal checkDeadline(params.deadline) {
|
||||
|
||||
// compute the liquidity amount
|
||||
uint128 liquidity;
|
||||
{
|
||||
(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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
PoolKey memory poolKey = PoolAddress.getPoolKey(params.token0, params.token1, FEE);
|
||||
(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");
|
||||
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);
|
||||
|
||||
TokenPosition memory position = _positions[posKey(token, params.tickLower, params.tickUpper)];
|
||||
if (liquidity == 0) {
|
||||
// create entry
|
||||
position = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
feeGrowthInside0LastX128: feeGrowthInside0LastX128,
|
||||
feeGrowthInside1LastX128: feeGrowthInside1LastX128
|
||||
});
|
||||
} else {
|
||||
// update entry
|
||||
updateFeesOwed(
|
||||
token0isWeth,
|
||||
token,
|
||||
uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
|
||||
position.liquidity,
|
||||
FixedPoint128.Q128
|
||||
)
|
||||
),
|
||||
uint128(
|
||||
FullMath.mulDiv(
|
||||
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
|
||||
position.liquidity,
|
||||
FixedPoint128.Q128
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
|
||||
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
|
||||
position.liquidity += liquidity;
|
||||
}
|
||||
}
|
||||
|
||||
function liquidatePosition(int24 tickLower, int24 tickUpper, uint256 amount0Min, uint256 amount1Min)
|
||||
internal
|
||||
returns (uint256 ethReceived, uint256 liquidity)
|
||||
{
|
||||
// load position
|
||||
TokenPosition storage position = _positions[posKey(tickLower, tickUpper)];
|
||||
|
||||
// burn and check slippage
|
||||
uint256 liquidity = position.liquidity;
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(tickLower, tickUpper, liquidity);
|
||||
require(amount0 >= amount0Min && amount1 >= amount1Min, "Price slippage check");
|
||||
harb.burn(token0isWeth ? amount1 : amount0);
|
||||
|
||||
// calculate and transfer fees
|
||||
bytes32 positionKey = PositionKey.compute(address(this), tickLower, tickUpper);
|
||||
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);
|
||||
uint256 ethOwed = position.ethOwed;
|
||||
ethOwed +=
|
||||
FullMath.mulDiv(
|
||||
(token0isWeth) ? feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128 : feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
|
||||
liquidity,
|
||||
FixedPoint128.Q128
|
||||
);
|
||||
weth.withdraw(ethOwed);
|
||||
(bool sent, ) = feeRecipient.call{value: ethOwed}("");
|
||||
require(sent, "Failed to send Ether");
|
||||
|
||||
// event, cleanup and return
|
||||
ethReceived = token0isWeth ? amount0 - ethOwed : amount1 - ethOwed;
|
||||
emit PositionLiquidated(tickLower, tickUpper, liquidity, ethReceived);
|
||||
delete position.liquidity;
|
||||
delete position.ethOwed;
|
||||
delete position.feeGrowthInside0LastX128;
|
||||
delete position.feeGrowthInside1LastX128;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
|
||||
function stretch(int24 tickLower, int24 tickUpper, uint256 deadline, uint256 amount0Min, uint256 amount1Min) external checkDeadline(deadline) {
|
||||
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(, int24 currentTick, , , , , ) = pool.slot0();
|
||||
|
||||
// Check if current tick is within the specified range
|
||||
int24 centerTick = tickLower + ((tickUpper - tickLower) / 2);
|
||||
// TODO: add hysteresis
|
||||
if (token0isWeth) {
|
||||
require(currentTick > centerTick && currentTick <= tickUpper, "Current tick out of range for stretch");
|
||||
} else {
|
||||
require(currentTick >= tickLower && currentTick < centerTick, "Current tick out of range for stretch");
|
||||
}
|
||||
|
||||
(uint256 ethReceived, uint256 oldliquidity) = liquidatePosition(tickLower, tickUpper, amount0Min, amount1Min);
|
||||
|
||||
uint256 liquidity;
|
||||
if (token0isWeth) {
|
||||
// extend the range up
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick + (currentTick - tickLower));
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethReceived
|
||||
);
|
||||
// calculate amount for new liquidity
|
||||
uint256 newAmount1 = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, liquidity
|
||||
);
|
||||
harb.mint(address(this), newAmount1);
|
||||
// TODO: addLiquidity(...)
|
||||
} else {
|
||||
// extend the range down
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(currentTick - (tickUpper - currentTick));
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethReceived
|
||||
);
|
||||
// calculate amount for new liquidity
|
||||
uint256 newAmount0 = LiquidityAmounts.getAmount1ForLiquidity(
|
||||
sqrtRatioAX96, sqrtRatioBX96, liquidity
|
||||
);
|
||||
harb.mint(address(this), newAmount0);
|
||||
// TODO: addLiquidity(...)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,6 +28,9 @@ contract Stake is IStake {
|
|||
error NoPermission(address requester, address owner);
|
||||
error PositionNotFound();
|
||||
|
||||
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 share, uint32 creationTime, uint32 taxRate);
|
||||
event PositionRemoved(uint256 indexed positionId, uint256 share, uint32 lastTaxTime);
|
||||
|
||||
struct StakingPosition {
|
||||
uint256 share;
|
||||
address owner;
|
||||
|
|
@ -40,7 +43,7 @@ contract Stake is IStake {
|
|||
IERC20Metadata private immutable tokenContract;
|
||||
address private immutable taxPool;
|
||||
uint256 public outstandingStake;
|
||||
uint256 private lastTokenId;
|
||||
uint256 public nextPositionId;
|
||||
uint256 public minStake;
|
||||
mapping(uint256 => StakingPosition) public positions;
|
||||
|
||||
|
|
@ -78,7 +81,7 @@ contract Stake is IStake {
|
|||
*/
|
||||
function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch)
|
||||
public
|
||||
returns (uint256)
|
||||
returns (uint256 positionId)
|
||||
{
|
||||
// check lower boundary
|
||||
uint256 sharesWanted = assetsToShares(assets, Math.Rounding.Down);
|
||||
|
|
@ -100,7 +103,7 @@ contract Stake is IStake {
|
|||
// dissolve position
|
||||
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
||||
_payTax(pos, 0);
|
||||
_exitPosition(pos);
|
||||
_exitPosition(positionsToSnatch[i], pos);
|
||||
}
|
||||
|
||||
// try to make a new position in the free space and hope it is big enough
|
||||
|
|
@ -113,7 +116,8 @@ contract Stake is IStake {
|
|||
SafeERC20.safeTransferFrom(tokenContract, msg.sender, address(this), assets);
|
||||
|
||||
// mint
|
||||
StakingPosition storage sp = positions[lastTokenId++];
|
||||
positionId = nextPositionId++;
|
||||
StakingPosition storage sp = positions[positionId];
|
||||
sp.share = sharesWanted;
|
||||
sp.owner = receiver;
|
||||
sp.lastTaxTime = uint32(block.timestamp);
|
||||
|
|
@ -121,18 +125,17 @@ contract Stake is IStake {
|
|||
sp.taxRate = taxRate;
|
||||
|
||||
outstandingStake += sharesWanted;
|
||||
|
||||
return lastTokenId;
|
||||
emit PositionCreated(positionId, sp.owner, sp.share, sp.creationTime, sp.taxRate);
|
||||
}
|
||||
|
||||
function exitPosition(uint256 positionID) public {
|
||||
StakingPosition storage pos = positions[positionID];
|
||||
function exitPosition(uint256 positionId) public {
|
||||
StakingPosition storage pos = positions[positionId];
|
||||
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(positionId, pos);
|
||||
}
|
||||
|
||||
function payTax(uint256 positionID) public {
|
||||
|
|
@ -178,10 +181,11 @@ contract Stake is IStake {
|
|||
}
|
||||
}
|
||||
|
||||
function _exitPosition(StakingPosition storage pos) private {
|
||||
function _exitPosition(uint256 positionId, StakingPosition storage pos) private {
|
||||
outstandingStake -= pos.share;
|
||||
address owner = pos.owner;
|
||||
uint256 assets = sharesToAssets(pos.share, Math.Rounding.Down);
|
||||
emit PositionRemoved(positionId, pos.share, pos.lastTaxTime);
|
||||
delete pos.owner;
|
||||
delete pos.creationTime;
|
||||
SafeERC20.safeTransfer(tokenContract, owner, assets);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import {TwabController} from "pt-v5-twab-controller/TwabController.sol";
|
|||
import {PoolAddress, PoolKey} from "@aperture/uni-v3-lib/PoolAddress.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import "../src/interfaces/IWETH9.sol";
|
||||
import "../src/Harb.sol";
|
||||
import {BaseLineLP} from "../src/BaseLineLP.sol";
|
||||
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
||||
|
||||
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
||||
|
|
@ -23,10 +24,10 @@ contract HarbTest is Test {
|
|||
Harb harb;
|
||||
IUniswapV3Factory factory;
|
||||
Stake stake;
|
||||
LiquidityManager liquidityManager;
|
||||
BaseLineLP liquidityManager;
|
||||
|
||||
function setUp() public {
|
||||
mainnetFork = vm.createFork(vm.envString("ETH_NODE_URI_MAINNET"), 19433030);
|
||||
mainnetFork = vm.createFork(vm.envString("ETH_NODE_URI_MAINNET"), 19461248);
|
||||
vm.selectFork(mainnetFork);
|
||||
weth = IWETH9(WETH);
|
||||
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
||||
|
|
@ -35,7 +36,7 @@ contract HarbTest is Test {
|
|||
factory.createPool(address(weth), address(harb), FEE);
|
||||
stake = new Stake(address(harb));
|
||||
harb.setStakingPool(address(stake));
|
||||
liquidityManager = new LiquidityManager(V3_FACTORY, WETH, address(harb));
|
||||
liquidityManager = new BaseLineLP(V3_FACTORY, WETH, address(harb));
|
||||
harb.setLiquidityManager(address(liquidityManager));
|
||||
}
|
||||
|
||||
|
|
@ -98,8 +99,9 @@ contract HarbTest is Test {
|
|||
// test stake when stake full
|
||||
{
|
||||
uint256[] memory empty;
|
||||
vm.expectRevert();
|
||||
//vm.expectRevert(abi.encodeWithSelector(ExceededAvailableStake.selector, account, amount, 0));
|
||||
vm.prank(account);
|
||||
vm.expectRevert(ExceededAvailableStake.selector);
|
||||
stake.snatch(amount, account, 2, empty);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue