new deployment with public position counter

This commit is contained in:
JulesCrown 2024-03-18 12:42:30 +01:00
parent 4e895f57e9
commit 2314ff51c1
8 changed files with 236 additions and 328 deletions

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

@ -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(...)
}
}
}

View file

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

View file

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