2024-04-11 07:28:54 +02:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
|
|
|
|
|
|
import "forge-std/Test.sol";
|
|
|
|
|
import "forge-std/console.sol";
|
|
|
|
|
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";
|
2024-04-27 07:04:33 +02:00
|
|
|
import "@aperture/uni-v3-lib/TickMath.sol";
|
2024-04-11 07:28:54 +02:00
|
|
|
import "../src/interfaces/IWETH9.sol";
|
2024-04-16 06:58:41 +02:00
|
|
|
import {WETH} from "solmate/tokens/WETH.sol";
|
2024-04-11 07:28:54 +02:00
|
|
|
import "../src/Harb.sol";
|
|
|
|
|
import {BaseLineLP} from "../src/BaseLineLP.sol";
|
|
|
|
|
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
|
|
|
|
|
|
|
|
|
address constant TAX_POOL = address(2);
|
|
|
|
|
// default fee of 1%
|
|
|
|
|
uint24 constant FEE = uint24(10_000);
|
|
|
|
|
|
|
|
|
|
contract BaseLineLPTest is Test {
|
|
|
|
|
uint256 mainnetFork;
|
|
|
|
|
IWETH9 weth;
|
|
|
|
|
Harb harb;
|
|
|
|
|
IUniswapV3Factory factory;
|
|
|
|
|
Stake stake;
|
|
|
|
|
BaseLineLP liquidityManager;
|
2024-04-27 07:04:33 +02:00
|
|
|
IUniswapV3Pool pool;
|
|
|
|
|
bool token0isWeth;
|
2024-04-11 07:28:54 +02:00
|
|
|
|
|
|
|
|
function sqrt(uint256 y) internal pure returns (uint256 z) {
|
|
|
|
|
if (y > 3) {
|
|
|
|
|
z = y;
|
|
|
|
|
uint256 x = y / 2 + 1;
|
|
|
|
|
while (x < z) {
|
|
|
|
|
z = x;
|
|
|
|
|
x = (y / x + x) / 2;
|
|
|
|
|
}
|
|
|
|
|
} else if (y != 0) {
|
|
|
|
|
z = 1;
|
|
|
|
|
}
|
|
|
|
|
// z is now the integer square root of y, or the closest integer to the square root of y.
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 09:18:21 +02:00
|
|
|
function initializePoolFor1Cent(address _pool) public {
|
2024-04-11 07:28:54 +02:00
|
|
|
uint256 price;
|
2024-04-27 07:04:33 +02:00
|
|
|
if (token0isWeth) {
|
2024-04-11 07:28:54 +02:00
|
|
|
// ETH as token0, so we are setting the price of 1 ETH in terms of token1 (USD cent)
|
2024-04-23 06:58:34 +02:00
|
|
|
price = 3000 * 10**20; // 1 ETH = 3700 USD, scaled by 10^18 for precision
|
2024-04-11 07:28:54 +02:00
|
|
|
} else {
|
|
|
|
|
// Token (valued at 1 USD cent) as token0, ETH as token1
|
|
|
|
|
// We invert the logic to represent the price of 1 token in terms of ETH
|
|
|
|
|
price = uint256(10**16) / 3700; // Adjust for 18 decimal places
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-23 06:58:34 +02:00
|
|
|
uint160 sqrtPriceX96 = uint160(sqrt(price) * 2**96 / 10**9); // Adjust sqrt value to 96-bit precision
|
2024-04-11 07:28:54 +02:00
|
|
|
|
|
|
|
|
// Initialize pool with the calculated sqrtPriceX96
|
2024-05-14 09:18:21 +02:00
|
|
|
IUniswapV3Pool(_pool).initialize(sqrtPriceX96);
|
2024-04-11 07:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-16 06:58:41 +02:00
|
|
|
function deployContract(bytes memory bytecode, bytes memory constructorArgs) internal returns (address addr) {
|
|
|
|
|
bytes memory deploymentData = abi.encodePacked(bytecode, constructorArgs);
|
|
|
|
|
assembly {
|
|
|
|
|
addr := create(0, add(deploymentData, 0x20), mload(deploymentData))
|
|
|
|
|
}
|
|
|
|
|
require(addr != address(0), "Contract deployment failed");
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-11 07:28:54 +02:00
|
|
|
function setUp() public {
|
2024-04-16 06:58:41 +02:00
|
|
|
bytes memory factoryBytecode = hex"60a060405234801561001057600080fd5b503060601b608052600380546001600160a01b031916339081179091556040516000907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c908290a36101f4600081815260046020527ffb8cf1d12598d1a039dd1d106665851a96aadf67d0d9ed76fceea282119208b7805462ffffff1916600a90811790915560405190929160008051602061614b83398151915291a3610bb8600081815260046020527f72dffa9b822156d9cf4b0090fa0b656bcb9cc2b2c60eb6acfc20a34f54b31743805462ffffff1916603c90811790915560405190929160008051602061614b83398151915291a3612710600081815260046020527f8cc740d51daa94ff54f33bd779c2d20149f524c340519b49181be5a08615f829805462ffffff191660c890811790915560405190929160008051602061614b83398151915291a360805160601c615fd7610174600039806105515250615fd76000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063890357301161005b578063890357301461013b5780638a7c195f146101855780638da5cb5b146101b0578063a1671295146101b85761007d565b806313af4035146100825780631698ee82146100aa57806322afcccb14610102575b600080fd5b6100a86004803603602081101561009857600080fd5b50356001600160a01b03166101f4565b005b6100e6600480360360608110156100c057600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff16610267565b604080516001600160a01b039092168252519081900360200190f35b6101246004803603602081101561011857600080fd5b503562ffffff16610293565b6040805160029290920b8252519081900360200190f35b6101436102a8565b604080516001600160a01b0396871681529486166020860152929094168383015262ffffff16606083015260029290920b608082015290519081900360a00190f35b6100a86004803603604081101561019b57600080fd5b5062ffffff813516906020013560020b6102de565b6100e66103a1565b6100e6600480360360608110156101ce57600080fd5b5080356001600160a01b03908116916020810135909116906040013562ffffff166103b0565b6003546001600160a01b0316331461020b57600080fd5b6003546040516001600160a01b038084169216907fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c90600090a3600380546001600160a01b0319166001600160a01b0392909216919091179055565b60056020908152600093845260408085208252928452828420905282529020546001600160a01b031681565b60046020526000908152604090205460020b81565b600054600154600280546001600160a01b03938416939283169281169162ffffff600160a01b83041691600160b81b9004900b85565b6003546001600160a01b031633146102f557600080fd5b620f42408262ffffff161061030957600080fd5b60008160020b13801561032057506140008160020b125b61032957600080fd5b62ffffff8216600090815260046020526040902054600290810b900b1561034f57600080fd5b62ffffff828116600081815260046020526040808220805462ffffff1916600287900b958616179055517fc66a3fdf07232cdd185febcc6579d408c241b47ae2f9907d84be655141eeaecc9190a35050565b6003546001600160a01b031681565b60006103ba610546565b826001600160a01b0316846001600160a01b031614156103d957600080fd5b600080846001600160a01b0316866001600160a01b0316106103fc5784866103ff565b85855b90925090506001600160a01b03821661041757600080fd5b62ffffff8416600090815260046020526040902054600290810b9081900b61043e57600080fd5b6001600160a01b0383811660009081526005602090815260408083208685168452825280832062ffffff8a168452909152902054161561047d57600080fd5b61048a308484888561057d565b6001600160a01b03808516600081815260056020818152604080842089871680865290835281852062ffffff8e168087529084528286208054988a166001600160a01b0319998a1681179091558287529484528286208787528452828620818752845294829020805490971684179096558051600289900b815291820192909252815195995091947f783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b71189281900390910190a45050509392505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461057b57600080fd5b565b6040805160a0810182526001600160a01b03878116808352878216602080850182905292881684860181905262ffffff888116606080880182905260028a810b6080998a01819052600080546001600160a01b03199081169099178155600180548a1689179055825490981686177fffffffffffffffffff000000ffffffffffffffffffffffffffffffffffffffff16600160a01b8502177fffffffffffff000000ffffffffffffffffffffffffffffffffffffffffffffff16600160b81b91830b909516029390931790925587518087019490945283880192909252828101919091528551808303909101815293019384905282519290
|
|
|
|
|
address factoryAddress = deployContract(factoryBytecode, "");
|
|
|
|
|
factory = IUniswapV3Factory(factoryAddress);
|
|
|
|
|
weth = IWETH9(address(new WETH()));
|
|
|
|
|
|
2024-04-11 07:28:54 +02:00
|
|
|
TwabController tc = new TwabController(60 * 60 * 24, uint32(block.timestamp));
|
2024-04-16 06:58:41 +02:00
|
|
|
harb = new Harb("HARB", "HARB", factoryAddress, address(weth), tc);
|
|
|
|
|
|
2024-04-27 07:04:33 +02:00
|
|
|
pool = IUniswapV3Pool(factory.createPool(address(weth), address(harb), FEE));
|
2024-04-11 07:28:54 +02:00
|
|
|
|
2024-04-27 07:04:33 +02:00
|
|
|
token0isWeth = address(weth) < address(harb);
|
|
|
|
|
initializePoolFor1Cent(address(pool));
|
2024-04-11 07:28:54 +02:00
|
|
|
|
|
|
|
|
stake = new Stake(address(harb));
|
|
|
|
|
harb.setStakingPool(address(stake));
|
2024-04-16 06:58:41 +02:00
|
|
|
liquidityManager = new BaseLineLP(factoryAddress, address(weth), address(harb));
|
2024-04-11 07:28:54 +02:00
|
|
|
harb.setLiquidityManager(address(liquidityManager));
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 07:08:13 +02:00
|
|
|
function testLP(address account) public {
|
|
|
|
|
vm.assume(account != address(0));
|
|
|
|
|
vm.assume(account != address(1)); // TWAB sponsorship address
|
|
|
|
|
vm.assume(account != address(2)); // tax pool address
|
2024-04-27 07:04:33 +02:00
|
|
|
vm.deal(account, 15 ether);
|
2024-04-11 07:28:54 +02:00
|
|
|
vm.prank(account);
|
|
|
|
|
(bool sent, ) = address(liquidityManager).call{value: 10 ether}("");
|
|
|
|
|
require(sent, "Failed to send Ether");
|
|
|
|
|
|
2024-04-27 07:04:33 +02:00
|
|
|
// Try to shift liquidity manager's state, expect failure due to initial state
|
2024-04-11 07:28:54 +02:00
|
|
|
vm.expectRevert();
|
|
|
|
|
liquidityManager.shift();
|
|
|
|
|
|
2024-04-29 06:27:28 +02:00
|
|
|
// Setup of liquidity
|
2024-04-11 07:28:54 +02:00
|
|
|
liquidityManager.slide();
|
2024-04-27 07:04:33 +02:00
|
|
|
|
|
|
|
|
|
2024-04-29 06:27:28 +02:00
|
|
|
// Small buy into Anchor
|
2024-04-27 07:04:33 +02:00
|
|
|
vm.prank(account);
|
|
|
|
|
weth.deposit{value: 5 ether}();
|
|
|
|
|
vm.prank(account);
|
|
|
|
|
weth.approve(address(this), 5 ether);
|
|
|
|
|
pool.swap(
|
|
|
|
|
account, // Recipient of the output tokens
|
|
|
|
|
token0isWeth,
|
|
|
|
|
int256(0.5 ether),
|
|
|
|
|
token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
|
|
|
|
|
abi.encode(account, int256(0.5 ether), true)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
liquidityManager.shift();
|
2024-04-29 06:27:28 +02:00
|
|
|
|
|
|
|
|
// large buy into discovery
|
2024-05-14 09:18:21 +02:00
|
|
|
pool.swap(
|
2024-04-29 06:27:28 +02:00
|
|
|
account, // Recipient of the output tokens
|
|
|
|
|
token0isWeth,
|
|
|
|
|
int256(3 ether),
|
|
|
|
|
token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
|
|
|
|
|
abi.encode(account, int256(3 ether), true)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
liquidityManager.shift();
|
|
|
|
|
|
|
|
|
|
|
2024-05-14 09:18:21 +02:00
|
|
|
// sell into anchor
|
2024-04-29 06:27:28 +02:00
|
|
|
vm.prank(account);
|
|
|
|
|
harb.approve(address(this), 2000000 ether);
|
|
|
|
|
pool.swap(
|
|
|
|
|
account, // Recipient of the output tokens
|
|
|
|
|
!token0isWeth,
|
|
|
|
|
int256(300000 ether),
|
|
|
|
|
!token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
|
|
|
|
|
abi.encode(account, int256(300000 ether), false)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
liquidityManager.slide();
|
|
|
|
|
|
2024-05-14 09:18:21 +02:00
|
|
|
// large sell into floor
|
2024-04-29 06:27:28 +02:00
|
|
|
harb.setLiquidityManager(address(account));
|
|
|
|
|
vm.prank(account);
|
|
|
|
|
harb.mint(900000 ether);
|
|
|
|
|
harb.setLiquidityManager(address(liquidityManager));
|
2024-05-14 09:18:21 +02:00
|
|
|
pool.swap(
|
2024-04-29 06:27:28 +02:00
|
|
|
account, // Recipient of the output tokens
|
|
|
|
|
!token0isWeth,
|
|
|
|
|
int256(900000 ether),
|
|
|
|
|
!token0isWeth ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1,
|
|
|
|
|
abi.encode(account, int256(900000 ether), false)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
liquidityManager.slide();
|
2024-04-27 07:04:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*//////////////////////////////////////////////////////////////
|
|
|
|
|
CALLBACKS
|
|
|
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
|
|
function uniswapV3SwapCallback(
|
|
|
|
|
int256 amount0Delta,
|
|
|
|
|
int256 amount1Delta,
|
|
|
|
|
bytes calldata _data
|
|
|
|
|
)
|
|
|
|
|
external
|
|
|
|
|
{
|
|
|
|
|
require(amount0Delta > 0 || amount1Delta > 0);
|
|
|
|
|
|
|
|
|
|
(address seller, uint256 harbIn, bool buy) = abi.decode(_data, (address, uint256, bool));
|
|
|
|
|
|
|
|
|
|
(bool isExactInput, uint256 amountToPay) = amount0Delta > 0
|
|
|
|
|
? (!token0isWeth, uint256(amount0Delta))
|
|
|
|
|
: (token0isWeth, uint256(amount1Delta));
|
|
|
|
|
if (buy) {
|
|
|
|
|
weth.transferFrom(seller, msg.sender, amountToPay);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
require(isExactInput, "reason 1");
|
|
|
|
|
// check input amount
|
|
|
|
|
require(amountToPay == harbIn, "reason 2");
|
|
|
|
|
// transfer eth to univ3 pool
|
|
|
|
|
|
|
|
|
|
require(harb.transferFrom(seller, msg.sender, amountToPay), "reason 3");
|
2024-04-11 07:28:54 +02:00
|
|
|
}
|
2024-04-27 07:04:33 +02:00
|
|
|
|
|
|
|
|
receive() external payable {}
|
2024-04-11 07:28:54 +02:00
|
|
|
}
|