first deployment

This commit is contained in:
JulesCrown 2024-03-12 20:22:10 +01:00
parent 9279e0c045
commit 06581a0b8d
13 changed files with 180 additions and 225 deletions

3
onchain/.gitignore vendored
View file

@ -12,3 +12,6 @@ docs/
# Dotenv file
.env
.secret
/broadcast/

File diff suppressed because one or more lines are too long

View file

@ -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/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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