Add Solidity linting with solhint, Foundry formatter, and pre-commit hooks (#51)
## Changes ### Configuration - Added .solhint.json with recommended rules + custom config - 160 char line length (warn) - Double quotes enforcement (error) - Explicit visibility required (error) - Console statements allowed (scripts/tests need them) - Gas optimization warnings enabled - Ignores test/helpers/, lib/, out/, cache/, broadcast/ - Added foundry.toml [fmt] section - 160 char line length - 4-space tabs - Double quotes - Thousands separators for numbers - Sort imports enabled - Added .lintstagedrc.json for pre-commit auto-fix - Runs solhint --fix on .sol files - Runs forge fmt on .sol files - Added husky pre-commit hook via lint-staged ### NPM Scripts - lint:sol - run solhint - lint:sol:fix - auto-fix solhint issues - format:sol - format with forge fmt - format:sol:check - check formatting - lint / lint:fix - combined commands ### Code Changes - Added explicit visibility modifiers (internal) to constants in scripts and tests - Fixed quote style in DeployLocal.sol - All Solidity files formatted with forge fmt ## Verification - ✅ forge fmt --check passes - ✅ No solhint errors (warnings only) - ✅ forge build succeeds - ✅ forge test passes (107/107) resolves #44 Co-authored-by: johba <johba@harb.eth> Reviewed-on: https://codeberg.org/johba/harb/pulls/51
This commit is contained in:
parent
f8927b426e
commit
d7c2184ccf
45 changed files with 2853 additions and 1225 deletions
|
|
@ -1,6 +1,10 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
if [ -d "onchain" ]; then
|
||||
(cd onchain && npx lint-staged)
|
||||
fi
|
||||
|
||||
if [ -d "kraiken-lib" ]; then
|
||||
(cd kraiken-lib && npx lint-staged)
|
||||
fi
|
||||
|
|
|
|||
6
onchain/.husky/pre-commit
Normal file
6
onchain/.husky/pre-commit
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
cd "$(dirname -- "$0")/.."
|
||||
|
||||
npx lint-staged
|
||||
6
onchain/.lintstagedrc.json
Normal file
6
onchain/.lintstagedrc.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"**/*.sol": [
|
||||
"solhint --fix",
|
||||
"forge fmt"
|
||||
]
|
||||
}
|
||||
20
onchain/.solhint.json
Normal file
20
onchain/.solhint.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "solhint:recommended",
|
||||
"rules": {
|
||||
"compiler-version": ["error", "^0.8.0"],
|
||||
"func-visibility": ["error", {"ignoreConstructors": true}],
|
||||
"state-visibility": "error",
|
||||
"max-line-length": ["warn", 160],
|
||||
"quotes": ["error", "double"],
|
||||
"reason-string": ["warn", {"maxLength": 64}],
|
||||
"no-empty-blocks": "warn",
|
||||
"no-unused-vars": "warn",
|
||||
"no-console": "off",
|
||||
"code-complexity": "off",
|
||||
"function-max-lines": "off",
|
||||
"gas-custom-errors": "warn",
|
||||
"gas-calldata-parameters": "warn",
|
||||
"not-rely-on-time": "off",
|
||||
"avoid-low-level-calls": "off"
|
||||
}
|
||||
}
|
||||
5
onchain/.solhintignore
Normal file
5
onchain/.solhintignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
test/helpers/
|
||||
lib/
|
||||
out/
|
||||
cache/
|
||||
broadcast/
|
||||
|
|
@ -12,3 +12,13 @@ optimizer_runs = 200
|
|||
[rpc_endpoints]
|
||||
goerli = "${GOERLI_RPC_URL}"
|
||||
# Remappings in remappings.txt
|
||||
|
||||
[fmt]
|
||||
line_length = 160
|
||||
tab_width = 4
|
||||
bracket_spacing = true
|
||||
int_types = "long"
|
||||
multiline_func_header = "all"
|
||||
quote_style = "double"
|
||||
number_underscore = "thousands"
|
||||
sort_imports = true
|
||||
|
|
|
|||
1760
onchain/package-lock.json
generated
1760
onchain/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,19 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@uniswap/universal-router": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.3",
|
||||
"solhint": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:sol": "solhint 'src/**/*.sol' 'script/**/*.sol' 'test/**/*.sol'",
|
||||
"lint:sol:fix": "solhint --fix 'src/**/*.sol' 'script/**/*.sol' 'test/**/*.sol'",
|
||||
"format:sol": "forge fmt",
|
||||
"format:sol:check": "forge fmt --check",
|
||||
"lint": "npm run lint:sol && npm run format:sol:check",
|
||||
"lint:fix": "npm run lint:sol:fix && npm run format:sol",
|
||||
"prepare": "husky"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
|
||||
import { LiquidityManager } from "../src/LiquidityManager.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "forge-std/Script.sol";
|
||||
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
|
||||
|
|
@ -74,11 +75,7 @@ contract DeployBase is Script {
|
|||
address optimizerAddress;
|
||||
if (optimizer == address(0)) {
|
||||
Optimizer optimizerImpl = new Optimizer();
|
||||
bytes memory params = abi.encodeWithSignature(
|
||||
"initialize(address,address)",
|
||||
address(kraiken),
|
||||
address(stake)
|
||||
);
|
||||
bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(kraiken), address(stake));
|
||||
ERC1967Proxy proxy = new ERC1967Proxy(address(optimizerImpl), params);
|
||||
optimizerAddress = address(proxy);
|
||||
console.log("Optimizer deployed at:", optimizerAddress);
|
||||
|
|
@ -88,12 +85,7 @@ contract DeployBase is Script {
|
|||
}
|
||||
|
||||
// Deploy LiquidityManager
|
||||
liquidityManager = new LiquidityManager(
|
||||
v3Factory,
|
||||
weth,
|
||||
address(kraiken),
|
||||
optimizerAddress
|
||||
);
|
||||
liquidityManager = new LiquidityManager(v3Factory, weth, address(kraiken), optimizerAddress);
|
||||
console.log("LiquidityManager deployed at:", address(liquidityManager));
|
||||
|
||||
// Set fee destination
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {DeployBase} from "./DeployBase.sol";
|
||||
import { DeployBase } from "./DeployBase.sol";
|
||||
|
||||
/**
|
||||
* @title DeployBaseMainnet
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {DeployBase} from "./DeployBase.sol";
|
||||
import { DeployBase } from "./DeployBase.sol";
|
||||
|
||||
/**
|
||||
* @title DeployBaseSepolia
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
|
||||
import { LiquidityManager } from "../src/LiquidityManager.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "forge-std/Script.sol";
|
||||
|
||||
/**
|
||||
* @title DeployLocal
|
||||
|
|
@ -19,12 +20,12 @@ import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
|||
contract DeployLocal is Script {
|
||||
using UniswapHelpers for IUniswapV3Pool;
|
||||
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
uint24 internal constant FEE = uint24(10_000);
|
||||
|
||||
// Configuration
|
||||
address constant feeDest = 0xf6a3eef9088A255c32b6aD2025f83E57291D9011;
|
||||
address constant weth = 0x4200000000000000000000000000000000000006;
|
||||
address constant v3Factory = 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24;
|
||||
address internal constant feeDest = 0xf6a3eef9088A255c32b6aD2025f83E57291D9011;
|
||||
address internal constant weth = 0x4200000000000000000000000000000000000006;
|
||||
address internal constant v3Factory = 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24;
|
||||
|
||||
// Deployed contracts
|
||||
Kraiken public kraiken;
|
||||
|
|
@ -87,22 +88,13 @@ contract DeployLocal is Script {
|
|||
|
||||
// Deploy Optimizer
|
||||
Optimizer optimizerImpl = new Optimizer();
|
||||
bytes memory params = abi.encodeWithSignature(
|
||||
"initialize(address,address)",
|
||||
address(kraiken),
|
||||
address(stake)
|
||||
);
|
||||
bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(kraiken), address(stake));
|
||||
ERC1967Proxy proxy = new ERC1967Proxy(address(optimizerImpl), params);
|
||||
address optimizerAddress = address(proxy);
|
||||
console.log("\n[4/6] Optimizer deployed:", optimizerAddress);
|
||||
|
||||
// Deploy LiquidityManager
|
||||
liquidityManager = new LiquidityManager(
|
||||
v3Factory,
|
||||
weth,
|
||||
address(kraiken),
|
||||
optimizerAddress
|
||||
);
|
||||
liquidityManager = new LiquidityManager(v3Factory, weth, address(kraiken), optimizerAddress);
|
||||
console.log("\n[5/6] LiquidityManager deployed:", address(liquidityManager));
|
||||
|
||||
// Configure contracts
|
||||
|
|
@ -126,7 +118,7 @@ contract DeployLocal is Script {
|
|||
console.log("1. Fund LiquidityManager with ETH:");
|
||||
console.log(" cast send", address(liquidityManager), "--value 0.1ether");
|
||||
console.log("2. Call recenter to initialize positions:");
|
||||
console.log(" cast send", address(liquidityManager), '"recenter()"');
|
||||
console.log(" cast send", address(liquidityManager), "\"recenter()\"");
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Script.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
|
||||
import { LiquidityManager } from "../src/LiquidityManager.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
import "../src/Stake.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import {ERC1967Proxy} from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "forge-std/Script.sol";
|
||||
|
||||
uint24 constant FEE = uint24(10_000);
|
||||
|
||||
contract DeployScript is Script {
|
||||
using UniswapHelpers for IUniswapV3Pool;
|
||||
|
||||
bool token0isWeth;
|
||||
address feeDest;
|
||||
address weth;
|
||||
address v3Factory;
|
||||
address twabc;
|
||||
bool internal token0isWeth;
|
||||
address internal feeDest;
|
||||
address internal weth;
|
||||
address internal v3Factory;
|
||||
address internal twabc;
|
||||
|
||||
function run() public {
|
||||
string memory seedPhrase = vm.readFile(".secret");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import {ERC20Permit} from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||
import { ERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import { Math } from "@openzeppelin/utils/math/Math.sol";
|
||||
|
||||
/**
|
||||
* @title stakeable ERC20 Token
|
||||
|
|
@ -42,7 +42,7 @@ contract Kraiken is ERC20, ERC20Permit {
|
|||
* @param name_ The name of the token
|
||||
* @param symbol_ The symbol of the token
|
||||
*/
|
||||
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) ERC20Permit(name_) {}
|
||||
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) ERC20Permit(name_) { }
|
||||
|
||||
/**
|
||||
* @notice Sets the address for the liquidityManager. Used once post-deployment to initialize the contract.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@uniswap-v3-periphery/libraries/PositionKey.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.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 {Kraiken} from "./Kraiken.sol";
|
||||
import {Optimizer} from "./Optimizer.sol";
|
||||
import "./abstracts/ThreePositionStrategy.sol";
|
||||
import { Kraiken } from "./Kraiken.sol";
|
||||
import { Optimizer } from "./Optimizer.sol";
|
||||
|
||||
import "./abstracts/PriceOracle.sol";
|
||||
import "./abstracts/ThreePositionStrategy.sol";
|
||||
import "./interfaces/IWETH9.sol";
|
||||
import "@aperture/uni-v3-lib/CallbackValidation.sol";
|
||||
import "@aperture/uni-v3-lib/PoolAddress.sol";
|
||||
import "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "@uniswap-v3-periphery/libraries/PositionKey.sol";
|
||||
|
||||
/**
|
||||
* @title LiquidityManager
|
||||
|
|
@ -82,7 +83,7 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
|
|||
// Handle WETH conversion
|
||||
uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed;
|
||||
if (weth.balanceOf(address(this)) < ethOwed) {
|
||||
weth.deposit{value: address(this).balance}();
|
||||
weth.deposit{ value: address(this).balance }();
|
||||
}
|
||||
|
||||
// Transfer tokens to pool
|
||||
|
|
@ -142,12 +143,7 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
|
|||
}
|
||||
|
||||
// Get optimizer parameters and set new positions
|
||||
try optimizer.getLiquidityParams() returns (
|
||||
uint256 capitalInefficiency,
|
||||
uint256 anchorShare,
|
||||
uint24 anchorWidth,
|
||||
uint256 discoveryDepth
|
||||
) {
|
||||
try optimizer.getLiquidityParams() returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
// Clamp parameters to valid ranges
|
||||
PositionParams memory params = PositionParams({
|
||||
capitalInefficiency: (capitalInefficiency > 10 ** 18) ? 10 ** 18 : capitalInefficiency,
|
||||
|
|
@ -160,11 +156,11 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
|
|||
} catch {
|
||||
// Fallback to default parameters if optimizer fails
|
||||
PositionParams memory defaultParams = PositionParams({
|
||||
capitalInefficiency: 5 * 10 ** 17, // 50%
|
||||
anchorShare: 5 * 10 ** 17, // 50%
|
||||
anchorWidth: 50, // 50%
|
||||
discoveryDepth: 5 * 10 ** 17 // 50%
|
||||
});
|
||||
capitalInefficiency: 5 * 10 ** 17, // 50%
|
||||
anchorShare: 5 * 10 ** 17, // 50%
|
||||
anchorWidth: 50, // 50%
|
||||
discoveryDepth: 5 * 10 ** 17 // 50%
|
||||
});
|
||||
|
||||
_setPositions(currentTick, defaultParams);
|
||||
}
|
||||
|
|
@ -180,19 +176,10 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
|
|||
TokenPosition storage position = positions[Stage(i)];
|
||||
if (position.liquidity > 0) {
|
||||
// Burn liquidity and collect tokens + fees
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(
|
||||
position.tickLower,
|
||||
position.tickUpper,
|
||||
position.liquidity
|
||||
);
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
||||
|
||||
(uint256 collected0, uint256 collected1) = pool.collect(
|
||||
address(this),
|
||||
position.tickLower,
|
||||
position.tickUpper,
|
||||
type(uint128).max,
|
||||
type(uint128).max
|
||||
);
|
||||
(uint256 collected0, uint256 collected1) =
|
||||
pool.collect(address(this), position.tickLower, position.tickUpper, type(uint128).max, type(uint128).max);
|
||||
|
||||
// Calculate fees
|
||||
fee0 += collected0 - amount0;
|
||||
|
|
@ -230,7 +217,7 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
|
|||
}
|
||||
|
||||
/// @notice Allow contract to receive ETH
|
||||
receive() external payable {}
|
||||
receive() external payable { }
|
||||
|
||||
// ========================================
|
||||
// ABSTRACT FUNCTION IMPLEMENTATIONS
|
||||
|
|
@ -259,11 +246,7 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
|
|||
/// @notice Implementation of abstract function from ThreePositionStrategy
|
||||
function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal override {
|
||||
pool.mint(address(this), tickLower, tickUpper, liquidity, abi.encode(poolKey));
|
||||
positions[stage] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: tickLower,
|
||||
tickUpper: tickUpper
|
||||
});
|
||||
positions[stage] = TokenPosition({ liquidity: liquidity, tickLower: tickLower, tickUpper: tickUpper });
|
||||
}
|
||||
|
||||
/// @notice Implementation of abstract function from ThreePositionStrategy
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Kraiken} from "./Kraiken.sol";
|
||||
import {Stake} from "./Stake.sol";
|
||||
import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
import { Kraiken } from "./Kraiken.sol";
|
||||
import { Stake } from "./Stake.sol";
|
||||
|
||||
import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
|
||||
/**
|
||||
* @title Optimizer
|
||||
|
|
@ -62,7 +63,7 @@ contract Optimizer is Initializable, UUPSUpgradeable {
|
|||
}
|
||||
}
|
||||
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {}
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { }
|
||||
|
||||
/**
|
||||
* @notice Calculates the sentiment based on the average tax rate and the percentage staked.
|
||||
|
|
@ -70,11 +71,7 @@ contract Optimizer is Initializable, UUPSUpgradeable {
|
|||
* @param percentageStaked The percentage (in 1e18 precision) of the authorized stake that is currently staked.
|
||||
* @return sentimentValue A value in the range 0 to 1e18 where 1e18 represents the worst sentiment.
|
||||
*/
|
||||
function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked)
|
||||
public
|
||||
pure
|
||||
returns (uint256 sentimentValue)
|
||||
{
|
||||
function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) {
|
||||
// Ensure percentageStaked doesn't exceed 100%
|
||||
require(percentageStaked <= 1e18, "Invalid percentage staked");
|
||||
|
||||
|
|
@ -142,11 +139,7 @@ contract Optimizer is Initializable, UUPSUpgradeable {
|
|||
*
|
||||
* Final width is clamped between 10 (minimum safe) and 80 (maximum effective)
|
||||
*/
|
||||
function _calculateAnchorWidth(uint256 percentageStaked, uint256 averageTaxRate)
|
||||
internal
|
||||
pure
|
||||
returns (uint24)
|
||||
{
|
||||
function _calculateAnchorWidth(uint256 percentageStaked, uint256 averageTaxRate) internal pure returns (uint24) {
|
||||
// Base width: 40% is our neutral starting point
|
||||
int256 baseWidth = 40;
|
||||
|
||||
|
|
@ -196,11 +189,7 @@ contract Optimizer is Initializable, UUPSUpgradeable {
|
|||
* - High avg tax rate → Expects volatility → Wider anchor to reduce rebalancing
|
||||
* - Low avg tax rate → Expects stability → Narrower anchor for fee collection
|
||||
*/
|
||||
function getLiquidityParams()
|
||||
external
|
||||
view
|
||||
returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
|
||||
{
|
||||
function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
uint256 percentageStaked = stake.getPercentageStaked();
|
||||
uint256 averageTaxRate = stake.getAverageTaxRate();
|
||||
uint256 sentiment = calculateSentiment(averageTaxRate, percentageStaked);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {IERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import {IERC20Metadata} from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import {ERC20Permit} from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
|
||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||
import {Kraiken} from "./Kraiken.sol";
|
||||
import { Kraiken } from "./Kraiken.sol";
|
||||
import { IERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
|
||||
import { ERC20Permit } from "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import { IERC20Metadata } from "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
|
||||
import { Math } from "@openzeppelin/utils/math/Math.sol";
|
||||
|
||||
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
||||
error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare);
|
||||
|
|
@ -42,38 +42,8 @@ contract Stake {
|
|||
uint256 internal constant MAX_STAKE = 20; // 20% of KRAIKEN supply
|
||||
uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3; //this duration is the minimum basis for fee calculation, regardless of actual holding time.
|
||||
// the tax rates are discrete to prevent users from snatching by micro incroments of tax
|
||||
uint256[] public TAX_RATES = [
|
||||
1,
|
||||
3,
|
||||
5,
|
||||
8,
|
||||
12,
|
||||
18,
|
||||
24,
|
||||
30,
|
||||
40,
|
||||
50,
|
||||
60,
|
||||
80,
|
||||
100,
|
||||
130,
|
||||
180,
|
||||
250,
|
||||
320,
|
||||
420,
|
||||
540,
|
||||
700,
|
||||
920,
|
||||
1200,
|
||||
1600,
|
||||
2000,
|
||||
2600,
|
||||
3400,
|
||||
4400,
|
||||
5700,
|
||||
7500,
|
||||
9700
|
||||
];
|
||||
uint256[] public TAX_RATES =
|
||||
[1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];
|
||||
// this is the base for the values in the array above: e.g. 1/100 = 1%
|
||||
uint256 internal constant TAX_RATE_BASE = 100;
|
||||
/**
|
||||
|
|
@ -85,12 +55,8 @@ contract Stake {
|
|||
error NoPermission(address requester, address owner);
|
||||
error PositionNotFound(uint256 positionId, address requester);
|
||||
|
||||
event PositionCreated(
|
||||
uint256 indexed positionId, address indexed owner, uint256 kraikenDeposit, uint256 share, uint32 taxRate
|
||||
);
|
||||
event PositionTaxPaid(
|
||||
uint256 indexed positionId, address indexed owner, uint256 taxPaid, uint256 newShares, uint256 taxRate
|
||||
);
|
||||
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 kraikenDeposit, uint256 share, uint32 taxRate);
|
||||
event PositionTaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxPaid, uint256 newShares, uint256 taxRate);
|
||||
event PositionRateHiked(uint256 indexed positionId, address indexed owner, uint256 newTaxRate);
|
||||
event PositionShrunk(uint256 indexed positionId, address indexed owner, uint256 newShares, uint256 kraikenPayout);
|
||||
event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 kraikenPayout);
|
||||
|
|
@ -123,7 +89,7 @@ contract Stake {
|
|||
taxReceiver = _taxReceiver;
|
||||
totalSupply = 10 ** (kraiken.decimals() + DECIMAL_OFFSET);
|
||||
// start counting somewhere
|
||||
nextPositionId = 654321;
|
||||
nextPositionId = 654_321;
|
||||
// Initialize totalSharesAtTaxRate array
|
||||
totalSharesAtTaxRate = new uint256[](TAX_RATES.length);
|
||||
}
|
||||
|
|
@ -136,13 +102,10 @@ contract Stake {
|
|||
function _payTax(uint256 positionId, StakingPosition storage pos, uint256 taxFloorDuration) private {
|
||||
// existance of position should be checked before
|
||||
// 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);
|
||||
uint256 taxAmountDue =
|
||||
assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||
uint256 taxAmountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||
if (taxAmountDue >= assetsBefore) {
|
||||
// can not pay more tax than value of position
|
||||
taxAmountDue = assetsBefore;
|
||||
|
|
@ -214,10 +177,7 @@ contract Stake {
|
|||
/// @param positionsToSnatch Array of position IDs that the new position will replace by snatching.
|
||||
/// @return positionId The ID of the newly created staking position.
|
||||
/// @dev Handles staking logic, including tax rate validation and position merging or dissolving.
|
||||
function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch)
|
||||
public
|
||||
returns (uint256 positionId)
|
||||
{
|
||||
function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch) public returns (uint256 positionId) {
|
||||
// check lower boundary
|
||||
uint256 sharesWanted = assetsToShares(assets);
|
||||
{
|
||||
|
|
@ -328,7 +288,10 @@ contract Stake {
|
|||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external returns (uint256 positionId) {
|
||||
)
|
||||
external
|
||||
returns (uint256 positionId)
|
||||
{
|
||||
ERC20Permit(address(kraiken)).permit(receiver, address(this), assets, deadline, v, r, s);
|
||||
return snatch(assets, receiver, taxRate, positionsToSnatch);
|
||||
}
|
||||
|
|
@ -389,9 +352,7 @@ contract Stake {
|
|||
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);
|
||||
amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "@openzeppelin/utils/math/SignedMath.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
|
||||
/**
|
||||
* @title PriceOracle
|
||||
|
|
@ -55,7 +55,11 @@ abstract contract PriceOracle {
|
|||
int24 centerTick,
|
||||
int24 tickSpacing,
|
||||
bool token0isWeth
|
||||
) internal pure returns (bool isUp, bool isEnough) {
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bool isUp, bool isEnough)
|
||||
{
|
||||
uint256 minAmplitude = uint24(tickSpacing) * 2;
|
||||
|
||||
// Determine the correct comparison direction based on token0isWeth
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||
import "../libraries/UniswapMath.sol";
|
||||
import "../VWAPTracker.sol";
|
||||
import "../libraries/UniswapMath.sol";
|
||||
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import { Math } from "@openzeppelin/utils/math/Math.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
|
||||
/**
|
||||
* @title ThreePositionStrategy
|
||||
|
|
@ -27,7 +27,7 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
/// @notice Tick spacing for the pool (base spacing)
|
||||
int24 internal constant TICK_SPACING = 200;
|
||||
/// @notice Discovery spacing (3x current price in ticks - 11000 ticks = ~3x price)
|
||||
int24 internal constant DISCOVERY_SPACING = 11000;
|
||||
int24 internal constant DISCOVERY_SPACING = 11_000;
|
||||
/// @notice Minimum discovery depth multiplier
|
||||
uint128 internal constant MIN_DISCOVERY_DEPTH = 200;
|
||||
|
||||
|
|
@ -97,7 +97,10 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
int24 currentTick,
|
||||
uint256 anchorEthBalance,
|
||||
PositionParams memory params
|
||||
) internal returns (uint256 pulledKraiken, uint128 anchorLiquidity) {
|
||||
)
|
||||
internal
|
||||
returns (uint256 pulledKraiken, uint128 anchorLiquidity)
|
||||
{
|
||||
// Enforce anchor range of 1% to 100% of the price
|
||||
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
||||
|
||||
|
|
@ -126,25 +129,15 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
/// @param anchorLiquidity Liquidity amount from anchor position
|
||||
/// @param params Position parameters
|
||||
/// @return discoveryAmount Amount of KRAIKEN used for discovery
|
||||
function _setDiscoveryPosition(
|
||||
int24 currentTick,
|
||||
uint128 anchorLiquidity,
|
||||
PositionParams memory params
|
||||
) internal returns (uint256 discoveryAmount) {
|
||||
function _setDiscoveryPosition(int24 currentTick, uint128 anchorLiquidity, PositionParams memory params) internal returns (uint256 discoveryAmount) {
|
||||
currentTick = currentTick / TICK_SPACING * TICK_SPACING;
|
||||
bool token0isWeth = _isToken0Weth();
|
||||
|
||||
// Calculate anchor spacing (same as in anchor position)
|
||||
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
||||
|
||||
int24 tickLower = _clampToTickSpacing(
|
||||
token0isWeth ? currentTick - DISCOVERY_SPACING - anchorSpacing : currentTick + anchorSpacing,
|
||||
TICK_SPACING
|
||||
);
|
||||
int24 tickUpper = _clampToTickSpacing(
|
||||
token0isWeth ? currentTick - anchorSpacing : currentTick + DISCOVERY_SPACING + anchorSpacing,
|
||||
TICK_SPACING
|
||||
);
|
||||
int24 tickLower = _clampToTickSpacing(token0isWeth ? currentTick - DISCOVERY_SPACING - anchorSpacing : currentTick + anchorSpacing, TICK_SPACING);
|
||||
int24 tickUpper = _clampToTickSpacing(token0isWeth ? currentTick - anchorSpacing : currentTick + DISCOVERY_SPACING + anchorSpacing, TICK_SPACING);
|
||||
|
||||
uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower);
|
||||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
|
|
@ -157,10 +150,7 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
int24 anchorWidth = 2 * anchorSpacing;
|
||||
|
||||
// Adjust for width difference: discovery liquidity = anchor liquidity * multiplier * (discovery width / anchor width)
|
||||
uint128 liquidity = uint128(
|
||||
uint256(anchorLiquidity) * discoveryMultiplier * uint256(int256(DISCOVERY_SPACING))
|
||||
/ (100 * uint256(int256(anchorWidth)))
|
||||
);
|
||||
uint128 liquidity = uint128(uint256(anchorLiquidity) * discoveryMultiplier * uint256(int256(DISCOVERY_SPACING)) / (100 * uint256(int256(anchorWidth))));
|
||||
|
||||
// Calculate discoveryAmount for floor position calculation
|
||||
if (token0isWeth) {
|
||||
|
|
@ -188,7 +178,9 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
uint256 pulledKraiken,
|
||||
uint256 discoveryAmount,
|
||||
PositionParams memory params
|
||||
) internal {
|
||||
)
|
||||
internal
|
||||
{
|
||||
bool token0isWeth = _isToken0Weth();
|
||||
|
||||
// Calculate outstanding supply after position minting
|
||||
|
|
@ -201,7 +193,6 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
uint256 ethBalance = _getEthBalance();
|
||||
int24 vwapTick;
|
||||
|
||||
|
||||
if (vwapX96 > 0) {
|
||||
// vwapX96 is price² in X96 format, need to convert to regular price
|
||||
// price = sqrt(price²) = sqrt(vwapX96) * 2^48 / 2^96 = sqrt(vwapX96) / 2^48
|
||||
|
|
@ -237,10 +228,7 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
|
||||
// Normalize and create floor position
|
||||
vwapTick = _clampToTickSpacing(vwapTick, TICK_SPACING);
|
||||
int24 floorTick = _clampToTickSpacing(
|
||||
token0isWeth ? vwapTick + TICK_SPACING : vwapTick - TICK_SPACING,
|
||||
TICK_SPACING
|
||||
);
|
||||
int24 floorTick = _clampToTickSpacing(token0isWeth ? vwapTick + TICK_SPACING : vwapTick - TICK_SPACING, TICK_SPACING);
|
||||
|
||||
// Use planned floor ETH balance, but fallback to remaining if insufficient
|
||||
uint256 remainingEthBalance = _getEthBalance();
|
||||
|
|
@ -249,17 +237,11 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
uint128 liquidity;
|
||||
if (token0isWeth) {
|
||||
// floor leg sits entirely above current tick when WETH is token0, so budget is token0
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
TickMath.getSqrtRatioAtTick(vwapTick),
|
||||
TickMath.getSqrtRatioAtTick(floorTick),
|
||||
actualFloorEthBalance
|
||||
);
|
||||
liquidity =
|
||||
LiquidityAmounts.getLiquidityForAmount0(TickMath.getSqrtRatioAtTick(vwapTick), TickMath.getSqrtRatioAtTick(floorTick), actualFloorEthBalance);
|
||||
} else {
|
||||
liquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
TickMath.getSqrtRatioAtTick(vwapTick),
|
||||
TickMath.getSqrtRatioAtTick(floorTick),
|
||||
actualFloorEthBalance
|
||||
);
|
||||
liquidity =
|
||||
LiquidityAmounts.getLiquidityForAmount1(TickMath.getSqrtRatioAtTick(vwapTick), TickMath.getSqrtRatioAtTick(floorTick), actualFloorEthBalance);
|
||||
}
|
||||
|
||||
_mintPosition(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
|
||||
library UniswapHelpers {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import { ABDKMath64x64 } from "@abdk/ABDKMath64x64.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
||||
import {ABDKMath64x64} from "@abdk/ABDKMath64x64.sol";
|
||||
import { Math } from "@openzeppelin/utils/math/Math.sol";
|
||||
|
||||
/**
|
||||
* @title UniswapMath
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "../src/Kraiken.sol";
|
||||
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console.sol";
|
||||
import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
|
||||
contract KraikenTest is Test {
|
||||
Kraiken kraiken;
|
||||
address stakingPool;
|
||||
address liquidityPool;
|
||||
address liquidityManager;
|
||||
Kraiken internal kraiken;
|
||||
address internal stakingPool;
|
||||
address internal liquidityPool;
|
||||
address internal liquidityManager;
|
||||
|
||||
function setUp() public {
|
||||
kraiken = new Kraiken("KRAIKEN", "KRK");
|
||||
|
|
@ -127,8 +127,7 @@ contract KraikenTest is Test {
|
|||
uint256 initialStakingPoolBalance = kraiken.balanceOf(stakingPool);
|
||||
|
||||
mintAmount = bound(mintAmount, 1, 500 * 1e18);
|
||||
uint256 expectedNewStake =
|
||||
initialStakingPoolBalance * mintAmount / (initialTotalSupply - initialStakingPoolBalance);
|
||||
uint256 expectedNewStake = initialStakingPoolBalance * mintAmount / (initialTotalSupply - initialStakingPoolBalance);
|
||||
|
||||
// Expect Transfer events
|
||||
vm.expectEmit(true, true, true, true, address(kraiken));
|
||||
|
|
@ -139,11 +138,7 @@ contract KraikenTest is Test {
|
|||
uint256 expectedStakingPoolBalance = initialStakingPoolBalance + expectedNewStake;
|
||||
uint256 expectedTotalSupply = initialTotalSupply + mintAmount + expectedNewStake;
|
||||
|
||||
assertEq(
|
||||
kraiken.balanceOf(stakingPool),
|
||||
expectedStakingPoolBalance,
|
||||
"Staking pool balance did not adjust correctly after mint."
|
||||
);
|
||||
assertEq(kraiken.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after mint.");
|
||||
assertEq(kraiken.totalSupply(), expectedTotalSupply, "Total supply did not match expected after mint.");
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +159,7 @@ contract KraikenTest is Test {
|
|||
burnAmount = bound(burnAmount, 0, 200 * 1e18);
|
||||
uint256 initialTotalSupply = kraiken.totalSupply();
|
||||
uint256 initialStakingPoolBalance = kraiken.balanceOf(stakingPool);
|
||||
uint256 expectedExcessStake =
|
||||
initialStakingPoolBalance * burnAmount / (initialTotalSupply - initialStakingPoolBalance);
|
||||
uint256 expectedExcessStake = initialStakingPoolBalance * burnAmount / (initialTotalSupply - initialStakingPoolBalance);
|
||||
|
||||
vm.prank(address(liquidityManager));
|
||||
kraiken.burn(burnAmount);
|
||||
|
|
@ -173,11 +167,7 @@ contract KraikenTest is Test {
|
|||
uint256 expectedStakingPoolBalance = initialStakingPoolBalance - expectedExcessStake;
|
||||
uint256 expectedTotalSupply = initialTotalSupply - burnAmount - expectedExcessStake;
|
||||
|
||||
assertEq(
|
||||
kraiken.balanceOf(stakingPool),
|
||||
expectedStakingPoolBalance,
|
||||
"Staking pool balance did not adjust correctly after burn."
|
||||
);
|
||||
assertEq(kraiken.balanceOf(stakingPool), expectedStakingPoolBalance, "Staking pool balance did not adjust correctly after burn.");
|
||||
assertEq(kraiken.totalSupply(), expectedTotalSupply, "Total supply did not match expected after burn.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,24 +10,25 @@ pragma solidity ^0.8.19;
|
|||
* - Edge case classification and recovery
|
||||
* @dev Uses setUp() pattern for consistent test initialization
|
||||
*/
|
||||
import "forge-std/Test.sol";
|
||||
import { Kraiken } from "../src/Kraiken.sol";
|
||||
import "../src/interfaces/IWETH9.sol";
|
||||
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import { PoolAddress, PoolKey } from "@aperture/uni-v3-lib/PoolAddress.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import {WETH} from "solmate/tokens/WETH.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 "../src/interfaces/IWETH9.sol";
|
||||
import {Kraiken} from "../src/Kraiken.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import { WETH } from "solmate/tokens/WETH.sol";
|
||||
|
||||
import { LiquidityManager } from "../src/LiquidityManager.sol";
|
||||
|
||||
import {Stake, ExceededAvailableStake} from "../src/Stake.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import {ThreePositionStrategy} from "../src/abstracts/ThreePositionStrategy.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import {UniSwapHelper} from "./helpers/UniswapTestBase.sol";
|
||||
import {TestEnvironment} from "./helpers/TestBase.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import { ExceededAvailableStake, Stake } from "../src/Stake.sol";
|
||||
import { ThreePositionStrategy } from "../src/abstracts/ThreePositionStrategy.sol";
|
||||
import "../src/helpers/UniswapHelpers.sol";
|
||||
import "../test/mocks/MockOptimizer.sol";
|
||||
import { TestEnvironment } from "./helpers/TestBase.sol";
|
||||
import { UniSwapHelper } from "./helpers/UniswapTestBase.sol";
|
||||
|
||||
// Test constants
|
||||
uint24 constant FEE = uint24(10_000); // 1% fee
|
||||
|
|
@ -53,10 +54,8 @@ uint256 constant VWAP_TEST_BALANCE = 100 ether;
|
|||
|
||||
// Error handling constants
|
||||
bytes32 constant AMPLITUDE_ERROR = keccak256("amplitude not reached.");
|
||||
bytes32 constant EXPENSIVE_HARB_ERROR =
|
||||
keccak256("HARB extremely expensive: perform swap to normalize price before recenter");
|
||||
bytes32 constant PROTOCOL_DEATH_ERROR =
|
||||
keccak256("Protocol death: Insufficient ETH reserves to support HARB at extremely low prices");
|
||||
bytes32 constant EXPENSIVE_HARB_ERROR = keccak256("HARB extremely expensive: perform swap to normalize price before recenter");
|
||||
bytes32 constant PROTOCOL_DEATH_ERROR = keccak256("Protocol death: Insufficient ETH reserves to support HARB at extremely low prices");
|
||||
|
||||
// Dummy.sol
|
||||
contract Dummy {
|
||||
|
|
@ -184,7 +183,6 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// @notice Validates recenter operation results
|
||||
/// @param isUp Whether the recenter moved positions up or down
|
||||
function _validateRecenterResult(bool isUp) internal view {
|
||||
|
|
@ -203,26 +201,18 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
// assertGt(
|
||||
// liquidityResponse.ethFloor, liquidityResponse.ethAnchor, "slide - Floor should hold more ETH than Anchor"
|
||||
// );
|
||||
assertGt(
|
||||
liquidityResponse.harbergDiscovery,
|
||||
liquidityResponse.harbergAnchor * 5,
|
||||
"slide - Discovery should hold more HARB than Anchor"
|
||||
);
|
||||
assertGt(liquidityResponse.harbergDiscovery, liquidityResponse.harbergAnchor * 5, "slide - Discovery should hold more HARB than Anchor");
|
||||
|
||||
// Check anchor-discovery contiguity (depends on token ordering)
|
||||
if (token0isWeth) {
|
||||
// When WETH is token0, discovery comes before anchor
|
||||
assertEq(
|
||||
liquidityResponse.discoveryTickUpper,
|
||||
liquidityResponse.anchorTickLower,
|
||||
"Discovery and Anchor positions must be contiguous (WETH as token0)"
|
||||
liquidityResponse.discoveryTickUpper, liquidityResponse.anchorTickLower, "Discovery and Anchor positions must be contiguous (WETH as token0)"
|
||||
);
|
||||
} else {
|
||||
// When WETH is token1, discovery comes after anchor
|
||||
assertEq(
|
||||
liquidityResponse.anchorTickUpper,
|
||||
liquidityResponse.discoveryTickLower,
|
||||
"Anchor and Discovery positions must be contiguous (WETH as token1)"
|
||||
liquidityResponse.anchorTickUpper, liquidityResponse.discoveryTickLower, "Anchor and Discovery positions must be contiguous (WETH as token1)"
|
||||
);
|
||||
}
|
||||
assertEq(liquidityResponse.harbergFloor, 0, "slide - Floor should have no HARB");
|
||||
|
|
@ -253,7 +243,6 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// @notice Retrieves liquidity position information for a specific stage
|
||||
/// @param s The liquidity stage (FLOOR, ANCHOR, DISCOVERY)
|
||||
/// @return currentTick Current price tick of the pool
|
||||
|
|
@ -368,7 +357,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
|
||||
/// @notice Allows contract to receive ETH directly
|
||||
/// @dev Required for WETH unwrapping operations during testing
|
||||
receive() external payable {}
|
||||
receive() external payable { }
|
||||
|
||||
/// @notice Override to provide LiquidityManager reference for liquidity-aware functions
|
||||
/// @return liquidityManager The LiquidityManager contract instance
|
||||
|
|
@ -415,7 +404,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
// Fund account and convert to WETH
|
||||
vm.deal(account, accountBalance);
|
||||
vm.prank(account);
|
||||
weth.deposit{value: accountBalance}();
|
||||
weth.deposit{ value: accountBalance }();
|
||||
|
||||
// Setup initial liquidity
|
||||
recenterWithErrorHandling(false);
|
||||
|
|
@ -434,9 +423,9 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
|
||||
// Custom minimal setup
|
||||
deployProtocolWithTokenOrder(DEFAULT_TOKEN0_IS_WETH);
|
||||
vm.deal(account, 15000 ether);
|
||||
vm.deal(account, 15_000 ether);
|
||||
vm.prank(account);
|
||||
weth.deposit{value: 15000 ether}();
|
||||
weth.deposit{ value: 15_000 ether }();
|
||||
|
||||
// Grant recenter access
|
||||
vm.prank(feeDestination);
|
||||
|
|
@ -458,12 +447,12 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
(, int24 stage1Tick,,,,,) = pool.slot0();
|
||||
|
||||
// Stage 2: Additional push if not yet at extreme boundary
|
||||
if (stage1Tick < TickMath.MAX_TICK - 15000) {
|
||||
if (stage1Tick < TickMath.MAX_TICK - 15_000) {
|
||||
buyRaw(2500 ether);
|
||||
(, int24 stage2Tick,,,,,) = pool.slot0();
|
||||
|
||||
// Stage 3: Final push with remaining ETH if still needed
|
||||
if (stage2Tick < TickMath.MAX_TICK - 15000) {
|
||||
if (stage2Tick < TickMath.MAX_TICK - 15_000) {
|
||||
uint256 remaining = weth.balanceOf(account) - 500 ether; // Keep some ETH for safety
|
||||
buyRaw(remaining);
|
||||
}
|
||||
|
|
@ -472,7 +461,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
(, int24 postBuyTick,,,,,) = pool.slot0();
|
||||
|
||||
// Verify we reached extreme boundary condition
|
||||
int24 targetBoundary = TickMath.MAX_TICK - 15000; // 872272
|
||||
int24 targetBoundary = TickMath.MAX_TICK - 15_000; // 872272
|
||||
assertGe(postBuyTick, targetBoundary, "Should reach extreme expensive boundary to validate boundary behavior");
|
||||
|
||||
// Test successfully demonstrates reaching extreme tick boundaries with buyRaw()
|
||||
|
|
@ -527,11 +516,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
OTHER_ERROR
|
||||
}
|
||||
|
||||
function classifyFailure(bytes memory reason)
|
||||
internal
|
||||
view
|
||||
returns (FailureType failureType, string memory details)
|
||||
{
|
||||
function classifyFailure(bytes memory reason) internal view returns (FailureType failureType, string memory details) {
|
||||
if (reason.length >= 4) {
|
||||
bytes4 selector = bytes4(reason);
|
||||
|
||||
|
|
@ -539,9 +524,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
|
||||
if (selector == 0xae47f702) {
|
||||
// FullMulDivFailed()
|
||||
return (
|
||||
FailureType.ARITHMETIC_OVERFLOW, "FullMulDivFailed - arithmetic overflow in liquidity calculations"
|
||||
);
|
||||
return (FailureType.ARITHMETIC_OVERFLOW, "FullMulDivFailed - arithmetic overflow in liquidity calculations");
|
||||
}
|
||||
|
||||
if (selector == 0x4e487b71) {
|
||||
|
|
@ -648,7 +631,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
console.log("Details:", details);
|
||||
|
||||
// This might be acceptable if we're at extreme prices
|
||||
if (currentTick <= TickMath.MIN_TICK + 50000 || currentTick >= TickMath.MAX_TICK - 50000) {
|
||||
if (currentTick <= TickMath.MIN_TICK + 50_000 || currentTick >= TickMath.MAX_TICK - 50_000) {
|
||||
console.log("Overflow at extreme tick - this may be acceptable edge case handling");
|
||||
} else {
|
||||
console.log("Overflow at normal tick - this indicates a problem");
|
||||
|
|
@ -713,9 +696,9 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
|
||||
// Diagnose the scenario type
|
||||
console.log("\n=== SCENARIO DIAGNOSIS ===");
|
||||
if (postBuyTick >= TickMath.MAX_TICK - 15000) {
|
||||
if (postBuyTick >= TickMath.MAX_TICK - 15_000) {
|
||||
console.log("[DIAGNOSIS] EXTREME EXPENSIVE HARB - should trigger normalization");
|
||||
} else if (postBuyTick <= TickMath.MIN_TICK + 15000) {
|
||||
} else if (postBuyTick <= TickMath.MIN_TICK + 15_000) {
|
||||
console.log("[DIAGNOSIS] EXTREME CHEAP HARB - potential protocol death");
|
||||
} else {
|
||||
console.log("[DIAGNOSIS] NORMAL RANGE - may still have arithmetic issues");
|
||||
|
|
@ -797,9 +780,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
uint256 traderBalanceAfter = weth.balanceOf(account);
|
||||
|
||||
// Core unit test assertion: protocol should not allow trader profit
|
||||
assertGe(
|
||||
traderBalanceBefore, traderBalanceAfter, "Protocol must prevent trader profit through arbitrary trading"
|
||||
);
|
||||
assertGe(traderBalanceBefore, traderBalanceAfter, "Protocol must prevent trader profit through arbitrary trading");
|
||||
}
|
||||
|
||||
/// @notice Helper to execute a sequence of random trades and recentering
|
||||
|
|
@ -816,12 +797,12 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
|
||||
// Handle extreme price conditions to prevent test failures
|
||||
(, int24 currentTick,,,,,) = pool.slot0();
|
||||
if (currentTick < -887270) {
|
||||
if (currentTick < -887_270) {
|
||||
// Price too low - small buy to stabilize
|
||||
uint256 wethBal = weth.balanceOf(account);
|
||||
if (wethBal > 0) buy(wethBal / 100);
|
||||
}
|
||||
if (currentTick > 887270) {
|
||||
if (currentTick > 887_270) {
|
||||
// Price too high - small sell to stabilize
|
||||
uint256 harbBal = harberg.balanceOf(account);
|
||||
if (harbBal > 0) sell(harbBal / 100);
|
||||
|
|
@ -844,7 +825,6 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
recenterWithErrorHandling(true);
|
||||
}
|
||||
|
||||
|
||||
// ========================================
|
||||
// ANTI-ARBITRAGE STRATEGY TESTS
|
||||
// ========================================
|
||||
|
|
@ -898,7 +878,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
console.log("\n=== PHASE 4: Slippage Analysis ===");
|
||||
|
||||
uint256 netLoss = wethSpent - wethReceived;
|
||||
uint256 slippagePercentage = (netLoss * 10000) / wethSpent; // Basis points
|
||||
uint256 slippagePercentage = (netLoss * 10_000) / wethSpent; // Basis points
|
||||
|
||||
console.log("Net loss:", netLoss / 1e18, "ETH");
|
||||
console.log("Slippage:", slippagePercentage, "basis points");
|
||||
|
|
@ -935,7 +915,7 @@ contract LiquidityManagerTest is UniSwapHelper {
|
|||
// The large price movement is actually evidence that the anti-arbitrage mechanism works!
|
||||
// The slippage is massive (80% loss), proving the strategy is effective
|
||||
// Adjust expectations based on actual behavior - this is a feature, not a bug
|
||||
assertLt(absMovement, 100000, "Round-trip should not cause impossible price displacement");
|
||||
assertLt(absMovement, 100_000, "Round-trip should not cause impossible price displacement");
|
||||
|
||||
console.log("\n=== ANTI-ARBITRAGE STRATEGY VALIDATION COMPLETE ===");
|
||||
console.log("PASS: Round-trip slippage:", slippagePercentage, "basis points");
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "../src/Optimizer.sol";
|
||||
|
||||
import "./mocks/MockKraiken.sol";
|
||||
import "./mocks/MockStake.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console.sol";
|
||||
import "../src/Optimizer.sol";
|
||||
import "./mocks/MockStake.sol";
|
||||
import "./mocks/MockKraiken.sol";
|
||||
|
||||
contract OptimizerTest is Test {
|
||||
Optimizer optimizer;
|
||||
|
|
@ -21,11 +22,7 @@ contract OptimizerTest is Test {
|
|||
Optimizer implementation = new Optimizer();
|
||||
|
||||
// Deploy proxy and initialize
|
||||
bytes memory initData = abi.encodeWithSelector(
|
||||
Optimizer.initialize.selector,
|
||||
address(mockKraiken),
|
||||
address(mockStake)
|
||||
);
|
||||
bytes memory initData = abi.encodeWithSelector(Optimizer.initialize.selector, address(mockKraiken), address(mockStake));
|
||||
|
||||
// For simplicity, we'll test the implementation directly
|
||||
// In production, you'd use a proper proxy setup
|
||||
|
|
@ -180,8 +177,8 @@ contract OptimizerTest is Test {
|
|||
function testHighStakingHighTaxEdgeCase() public {
|
||||
// Set conditions that previously caused overflow
|
||||
// ~94.6% staked, ~96.7% tax rate
|
||||
mockStake.setPercentageStaked(946350908835331692);
|
||||
mockStake.setAverageTaxRate(966925542613630263);
|
||||
mockStake.setPercentageStaked(946_350_908_835_331_692);
|
||||
mockStake.setAverageTaxRate(966_925_542_613_630_263);
|
||||
|
||||
(uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) = optimizer.getLiquidityParams();
|
||||
|
||||
|
|
@ -226,8 +223,7 @@ contract OptimizerTest is Test {
|
|||
mockStake.setPercentageStaked(0.6e18);
|
||||
mockStake.setAverageTaxRate(0.4e18);
|
||||
|
||||
(uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) =
|
||||
optimizer.getLiquidityParams();
|
||||
(uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) = optimizer.getLiquidityParams();
|
||||
|
||||
uint256 sentiment = optimizer.getSentiment();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import {TestEnvironment} from "./helpers/TestBase.sol";
|
||||
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import {IWETH9} from "../src/interfaces/IWETH9.sol";
|
||||
import {Kraiken} from "../src/Kraiken.sol";
|
||||
import {Stake} from "../src/Stake.sol";
|
||||
import {LiquidityManager} from "../src/LiquidityManager.sol";
|
||||
import "../analysis/helpers/SwapExecutor.sol";
|
||||
import { Kraiken } from "../src/Kraiken.sol";
|
||||
import { LiquidityManager } from "../src/LiquidityManager.sol";
|
||||
import { Stake } from "../src/Stake.sol";
|
||||
import { IWETH9 } from "../src/interfaces/IWETH9.sol";
|
||||
|
||||
import "../test/mocks/BullMarketOptimizer.sol";
|
||||
import { TestEnvironment } from "./helpers/TestBase.sol";
|
||||
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title ReplayProfitableScenario
|
||||
|
|
@ -35,8 +36,7 @@ contract ReplayProfitableScenario is Test {
|
|||
BullMarketOptimizer optimizer = new BullMarketOptimizer();
|
||||
|
||||
// Use seed 1 setup (odd seed = false for first param)
|
||||
(,pool, weth, kraiken, stake, lm,, token0isWeth) =
|
||||
testEnv.setupEnvironmentWithOptimizer(false, feeDestination, address(optimizer));
|
||||
(, pool, weth, kraiken, stake, lm,, token0isWeth) = testEnv.setupEnvironmentWithOptimizer(false, feeDestination, address(optimizer));
|
||||
|
||||
// Fund exactly as in the recorded scenario
|
||||
vm.deal(address(lm), 200 ether);
|
||||
|
|
@ -45,13 +45,13 @@ contract ReplayProfitableScenario is Test {
|
|||
uint256 traderFund = 50 ether + (uint256(keccak256(abi.encodePacked(uint256(1), "trader"))) % 150 ether);
|
||||
vm.deal(trader, traderFund * 2);
|
||||
vm.prank(trader);
|
||||
weth.deposit{value: traderFund}();
|
||||
weth.deposit{ value: traderFund }();
|
||||
|
||||
// Whale gets specific amount based on seed 1
|
||||
uint256 whaleFund = 200 ether + (uint256(keccak256(abi.encodePacked(uint256(1), "whale"))) % 300 ether);
|
||||
vm.deal(whale, whaleFund * 2);
|
||||
vm.prank(whale);
|
||||
weth.deposit{value: whaleFund}();
|
||||
weth.deposit{ value: whaleFund }();
|
||||
|
||||
// Initial recenter
|
||||
vm.prank(feeDestination);
|
||||
|
|
@ -78,47 +78,47 @@ contract ReplayProfitableScenario is Test {
|
|||
|
||||
// Step 1: Trader buys 38 ETH worth
|
||||
console.log("\nStep 1: Trader BUY 38 ETH");
|
||||
_executeBuy(trader, 38215432537912335624);
|
||||
_executeBuy(trader, 38_215_432_537_912_335_624);
|
||||
_logTickChange();
|
||||
|
||||
// Step 2: Trader sells large amount of KRAIKEN
|
||||
console.log("\nStep 2: Trader SELL 2M KRAIKEN");
|
||||
_executeSell(trader, 2023617577713031308513047);
|
||||
_executeSell(trader, 2_023_617_577_713_031_308_513_047);
|
||||
_logTickChange();
|
||||
|
||||
// Step 3: Whale buys 132 ETH worth
|
||||
console.log("\nStep 3: Whale BUY 132 ETH");
|
||||
_executeBuy(whale, 132122625892942968181);
|
||||
_executeBuy(whale, 132_122_625_892_942_968_181);
|
||||
_logTickChange();
|
||||
|
||||
// Step 4: Trader sells
|
||||
console.log("\nStep 4: Trader SELL 1.5M KRAIKEN");
|
||||
_executeSell(trader, 1517713183284773481384785);
|
||||
_executeSell(trader, 1_517_713_183_284_773_481_384_785);
|
||||
_logTickChange();
|
||||
|
||||
// Step 5: Whale buys 66 ETH worth
|
||||
console.log("\nStep 5: Whale BUY 66 ETH");
|
||||
_executeBuy(whale, 66061312946471484091);
|
||||
_executeBuy(whale, 66_061_312_946_471_484_091);
|
||||
_logTickChange();
|
||||
|
||||
// Step 6: Trader sells
|
||||
console.log("\nStep 6: Trader SELL 1.1M KRAIKEN");
|
||||
_executeSell(trader, 1138284887463580111038589);
|
||||
_executeSell(trader, 1_138_284_887_463_580_111_038_589);
|
||||
_logTickChange();
|
||||
|
||||
// Step 7: Whale buys 33 ETH worth
|
||||
console.log("\nStep 7: Whale BUY 33 ETH");
|
||||
_executeBuy(whale, 33030656473235742045);
|
||||
_executeBuy(whale, 33_030_656_473_235_742_045);
|
||||
_logTickChange();
|
||||
|
||||
// Step 8: Trader sells
|
||||
console.log("\nStep 8: Trader SELL 853K KRAIKEN");
|
||||
_executeSell(trader, 853713665597685083278941);
|
||||
_executeSell(trader, 853_713_665_597_685_083_278_941);
|
||||
_logTickChange();
|
||||
|
||||
// Step 9: Final trader sell
|
||||
console.log("\nStep 9: Trader SELL 2.5M KRAIKEN (final)");
|
||||
_executeSell(trader, 2561140996793055249836826);
|
||||
_executeSell(trader, 2_561_140_996_793_055_249_836_826);
|
||||
_logTickChange();
|
||||
|
||||
// Check if we reached discovery
|
||||
|
|
@ -180,7 +180,7 @@ contract ReplayProfitableScenario is Test {
|
|||
// The scenario reached tick -119608 which was marked as discovery
|
||||
// This confirms the exploit works by reaching rarely-accessed liquidity zones
|
||||
|
||||
if (currentTick < -119000 && currentTick > -120000) {
|
||||
if (currentTick < -119_000 && currentTick > -120_000) {
|
||||
console.log("[CONFIRMED] Reached discovery zone around tick -119600");
|
||||
console.log("This zone has massive liquidity that's rarely accessed");
|
||||
console.log("Traders can exploit the liquidity imbalance for profit");
|
||||
|
|
@ -188,15 +188,15 @@ contract ReplayProfitableScenario is Test {
|
|||
}
|
||||
|
||||
function _executeFullScenario() internal {
|
||||
_executeBuy(trader, 38215432537912335624);
|
||||
_executeSell(trader, 2023617577713031308513047);
|
||||
_executeBuy(whale, 132122625892942968181);
|
||||
_executeSell(trader, 1517713183284773481384785);
|
||||
_executeBuy(whale, 66061312946471484091);
|
||||
_executeSell(trader, 1138284887463580111038589);
|
||||
_executeBuy(whale, 33030656473235742045);
|
||||
_executeSell(trader, 853713665597685083278941);
|
||||
_executeSell(trader, 2561140996793055249836826);
|
||||
_executeBuy(trader, 38_215_432_537_912_335_624);
|
||||
_executeSell(trader, 2_023_617_577_713_031_308_513_047);
|
||||
_executeBuy(whale, 132_122_625_892_942_968_181);
|
||||
_executeSell(trader, 1_517_713_183_284_773_481_384_785);
|
||||
_executeBuy(whale, 66_061_312_946_471_484_091);
|
||||
_executeSell(trader, 1_138_284_887_463_580_111_038_589);
|
||||
_executeBuy(whale, 33_030_656_473_235_742_045);
|
||||
_executeSell(trader, 853_713_665_597_685_083_278_941);
|
||||
_executeSell(trader, 2_561_140_996_793_055_249_836_826);
|
||||
}
|
||||
|
||||
function _executeBuy(address buyer, uint256 amount) internal {
|
||||
|
|
@ -209,7 +209,8 @@ contract ReplayProfitableScenario is Test {
|
|||
vm.prank(buyer);
|
||||
weth.transfer(address(executor), amount);
|
||||
|
||||
try executor.executeBuy(amount, buyer) {} catch {
|
||||
try executor.executeBuy(amount, buyer) { }
|
||||
catch {
|
||||
console.log(" [WARNING] Buy failed");
|
||||
}
|
||||
}
|
||||
|
|
@ -225,7 +226,8 @@ contract ReplayProfitableScenario is Test {
|
|||
vm.prank(seller);
|
||||
kraiken.transfer(address(executor), amount);
|
||||
|
||||
try executor.executeSell(amount, seller) {} catch {
|
||||
try executor.executeSell(amount, seller) { }
|
||||
catch {
|
||||
console.log(" [WARNING] Sell failed");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "../src/Kraiken.sol";
|
||||
import { Stake, TooMuchSnatch } from "../src/Stake.sol";
|
||||
import "./helpers/TestBase.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console.sol";
|
||||
import "../src/Kraiken.sol";
|
||||
import {TooMuchSnatch, Stake} from "../src/Stake.sol";
|
||||
import "./helpers/TestBase.sol";
|
||||
|
||||
contract StakeTest is TestConstants {
|
||||
Kraiken kraiken;
|
||||
|
|
@ -13,9 +13,7 @@ contract StakeTest is TestConstants {
|
|||
address liquidityPool;
|
||||
address liquidityManager;
|
||||
|
||||
event PositionCreated(
|
||||
uint256 indexed positionId, address indexed owner, uint256 kraikenDeposit, uint256 share, uint32 taxRate
|
||||
);
|
||||
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 kraikenDeposit, uint256 share, uint32 taxRate);
|
||||
event PositionRemoved(uint256 indexed positionId, address indexed owner, uint256 kraikenPayout);
|
||||
|
||||
function setUp() public {
|
||||
|
|
@ -58,15 +56,11 @@ contract StakeTest is TestConstants {
|
|||
uint256[] memory empty;
|
||||
uint256 sharesExpected = stakingPool.assetsToShares(stakeAmount);
|
||||
vm.expectEmit(address(stakingPool));
|
||||
emit PositionCreated(654321, staker, stakeAmount, sharesExpected, 1);
|
||||
emit PositionCreated(654_321, staker, stakeAmount, sharesExpected, 1);
|
||||
uint256 positionId = stakingPool.snatch(stakeAmount, staker, 1, empty);
|
||||
|
||||
// Check results
|
||||
assertEq(
|
||||
stakingPool.outstandingStake(),
|
||||
stakingPool.assetsToShares(stakeAmount),
|
||||
"Outstanding stake did not update correctly"
|
||||
);
|
||||
assertEq(stakingPool.outstandingStake(), stakingPool.assetsToShares(stakeAmount), "Outstanding stake did not update correctly");
|
||||
(uint256 share, address owner, uint32 creationTime,, uint32 taxRate) = stakingPool.positions(positionId);
|
||||
assertEq(stakingPool.sharesToAssets(share), stakeAmount, "Stake amount in position is incorrect");
|
||||
assertEq(owner, staker, "Stake owner is incorrect");
|
||||
|
|
@ -224,7 +218,7 @@ contract StakeTest is TestConstants {
|
|||
positionId2 = doSnatch(staker, stakeTwoThird, 29);
|
||||
|
||||
avgTaxRate = stakingPool.getAverageTaxRate();
|
||||
assertApproxEqRel(bp(denormTR(avgTaxRate)), 97000, 1e17);
|
||||
assertApproxEqRel(bp(denormTR(avgTaxRate)), 97_000, 1e17);
|
||||
|
||||
vm.startPrank(staker);
|
||||
stakingPool.exitPosition(positionId1);
|
||||
|
|
@ -263,11 +257,7 @@ contract StakeTest is TestConstants {
|
|||
kraiken.approve(address(stakingPool), tooSmallStake);
|
||||
|
||||
uint256[] memory empty;
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSelector(
|
||||
Stake.StakeTooLow.selector, staker, tooSmallStake, kraiken.previousTotalSupply() / 3000
|
||||
)
|
||||
);
|
||||
vm.expectRevert(abi.encodeWithSelector(Stake.StakeTooLow.selector, staker, tooSmallStake, kraiken.previousTotalSupply() / 3000));
|
||||
stakingPool.snatch(tooSmallStake, staker, 1, empty);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
|
@ -312,9 +302,7 @@ contract StakeTest is TestConstants {
|
|||
|
||||
uint256[] memory positions = new uint256[](1);
|
||||
positions[0] = positionId;
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSelector(TooMuchSnatch.selector, ambitiousStaker, 500000 ether, 1000000 ether, 1000000 ether)
|
||||
);
|
||||
vm.expectRevert(abi.encodeWithSelector(TooMuchSnatch.selector, ambitiousStaker, 500_000 ether, 1_000_000 ether, 1_000_000 ether));
|
||||
stakingPool.snatch(1 ether, ambitiousStaker, 20, positions);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
|
@ -404,7 +392,7 @@ contract StakeTest is TestConstants {
|
|||
uint256 taxBase = 100;
|
||||
|
||||
uint256 taxFractionForTime = taxRate * daysElapsed * 1 ether / daysInYear / taxBase;
|
||||
uint256 expectedShareAfterTax = (1 ether - taxFractionForTime) * 1000000;
|
||||
uint256 expectedShareAfterTax = (1 ether - taxFractionForTime) * 1_000_000;
|
||||
|
||||
assertTrue(share < shareBefore, "Share should decrease correctly after tax payment 1");
|
||||
assertEq(share, expectedShareAfterTax, "Share should decrease correctly after tax payment 2");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../src/VWAPTracker.sol";
|
||||
import "./mocks/MockVWAPTracker.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title VWAPTracker Test Suite
|
||||
|
|
@ -13,12 +13,11 @@ import "./mocks/MockVWAPTracker.sol";
|
|||
* - Adjusted VWAP with capital inefficiency
|
||||
* - Volume weighted price accumulation
|
||||
*/
|
||||
|
||||
contract VWAPTrackerTest is Test {
|
||||
MockVWAPTracker vwapTracker;
|
||||
|
||||
// Test constants
|
||||
uint256 constant SAMPLE_PRICE_X96 = 79228162514264337593543950336; // 1.0 in X96 format
|
||||
uint256 constant SAMPLE_PRICE_X96 = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96 format
|
||||
uint256 constant SAMPLE_FEE = 1 ether;
|
||||
uint256 constant CAPITAL_INEFFICIENCY = 5 * 10 ** 17; // 50%
|
||||
|
||||
|
|
@ -68,9 +67,7 @@ contract VWAPTrackerTest is Test {
|
|||
|
||||
uint256 expectedVWAP = (price1 * volume1 + price2 * volume2) / expectedTotalVolume;
|
||||
|
||||
assertEq(
|
||||
vwapTracker.cumulativeVolume(), expectedTotalVolume, "Total volume should be sum of individual volumes"
|
||||
);
|
||||
assertEq(vwapTracker.cumulativeVolume(), expectedTotalVolume, "Total volume should be sum of individual volumes");
|
||||
assertEq(vwapTracker.getVWAP(), expectedVWAP, "VWAP should be correctly weighted average");
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +163,7 @@ contract VWAPTrackerTest is Test {
|
|||
uint256 historicalVolume = 100 ether; // Large volume to establish strong historical weight
|
||||
|
||||
// Build up significant historical data at cheap prices
|
||||
for (uint i = 0; i < 10; i++) {
|
||||
for (uint256 i = 0; i < 10; i++) {
|
||||
vwapTracker.recordVolumeAndPrice(cheapPrice, historicalVolume);
|
||||
}
|
||||
|
||||
|
|
@ -231,9 +228,7 @@ contract VWAPTrackerTest is Test {
|
|||
|
||||
uint256 expectedAdjustedVWAP = 7 * baseVWAP / 10;
|
||||
|
||||
assertEq(
|
||||
adjustedVWAP, expectedAdjustedVWAP, "Adjusted VWAP with zero capital inefficiency should be 70% of base"
|
||||
);
|
||||
assertEq(adjustedVWAP, expectedAdjustedVWAP, "Adjusted VWAP with zero capital inefficiency should be 70% of base");
|
||||
}
|
||||
|
||||
function testAdjustedVWAPWithMaxCapitalInefficiency() public {
|
||||
|
|
@ -244,9 +239,7 @@ contract VWAPTrackerTest is Test {
|
|||
|
||||
uint256 expectedAdjustedVWAP = (7 * baseVWAP / 10) + baseVWAP;
|
||||
|
||||
assertEq(
|
||||
adjustedVWAP, expectedAdjustedVWAP, "Adjusted VWAP with max capital inefficiency should be 170% of base"
|
||||
);
|
||||
assertEq(adjustedVWAP, expectedAdjustedVWAP, "Adjusted VWAP with max capital inefficiency should be 170% of base");
|
||||
}
|
||||
|
||||
// ========================================
|
||||
|
|
@ -277,9 +270,9 @@ contract VWAPTrackerTest is Test {
|
|||
uint256[] memory prices = new uint256[](3);
|
||||
uint256[] memory fees = new uint256[](3);
|
||||
|
||||
prices[0] = 100000;
|
||||
prices[1] = 200000;
|
||||
prices[2] = 150000;
|
||||
prices[0] = 100_000;
|
||||
prices[1] = 200_000;
|
||||
prices[2] = 150_000;
|
||||
|
||||
fees[0] = 1000;
|
||||
fees[1] = 2000;
|
||||
|
|
@ -348,12 +341,12 @@ contract VWAPTrackerTest is Test {
|
|||
*/
|
||||
function testDoubleOverflowExtremeEthPriceScenario() public {
|
||||
// Set up post-compression state (simulate 1000x compression already occurred)
|
||||
uint256 maxSafeValue = type(uint256).max / 10**6; // Compression trigger point
|
||||
uint256 maxSafeValue = type(uint256).max / 10 ** 6; // Compression trigger point
|
||||
uint256 compressedValue = maxSafeValue; // Near threshold after compression
|
||||
|
||||
// Manually set post-compression state
|
||||
vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(compressedValue));
|
||||
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10**30)));
|
||||
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10 ** 30)));
|
||||
|
||||
// Calculate space available before next overflow
|
||||
uint256 availableSpace = type(uint256).max - compressedValue;
|
||||
|
|
@ -369,13 +362,13 @@ contract VWAPTrackerTest is Test {
|
|||
|
||||
// ASSERTIONS: Verify double overflow requires unrealistic conditions
|
||||
assertGt(requiredFee, 1000 ether, "Double overflow requires unrealistic fee > 1000 ETH");
|
||||
assertGt(requiredFee * extremeEthPriceUSD / 10**18, 1_000_000_000, "Required fee exceeds $1B USD");
|
||||
assertGt(requiredFee * extremeEthPriceUSD / 10 ** 18, 1_000_000_000, "Required fee exceeds $1B USD");
|
||||
|
||||
// Verify the mathematical relationship
|
||||
assertEq(minProductForOverflow, availableSpace / 100 + 1, "Overflow threshold calculation correct");
|
||||
|
||||
// Verify compression provides adequate protection
|
||||
assertGt(minProductForOverflow, 10**50, "Product threshold astronomically high");
|
||||
assertGt(minProductForOverflow, 10 ** 50, "Product threshold astronomically high");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -384,12 +377,12 @@ contract VWAPTrackerTest is Test {
|
|||
*/
|
||||
function testDoubleOverflowHyperinflatedHarbScenario() public {
|
||||
// Set up post-compression state (simulate 1000x compression already occurred)
|
||||
uint256 maxSafeValue = type(uint256).max / 10**6;
|
||||
uint256 maxSafeValue = type(uint256).max / 10 ** 6;
|
||||
uint256 compressedValue = maxSafeValue;
|
||||
|
||||
// Manually set post-compression state
|
||||
vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(compressedValue));
|
||||
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10**30)));
|
||||
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10 ** 30)));
|
||||
|
||||
// Calculate overflow requirements
|
||||
uint256 availableSpace = type(uint256).max - compressedValue;
|
||||
|
|
@ -405,13 +398,13 @@ contract VWAPTrackerTest is Test {
|
|||
|
||||
// ASSERTIONS: Verify double overflow requires unrealistic conditions
|
||||
assertGt(requiredFee, 100 ether, "Double overflow requires unrealistic fee > 100 ETH");
|
||||
assertGt(requiredFee * normalEthPrice / 10**18, 300_000, "Required fee exceeds $300k USD");
|
||||
assertGt(requiredFee * normalEthPrice / 10 ** 18, 300_000, "Required fee exceeds $300k USD");
|
||||
|
||||
// Verify HARB price assumption is unrealistic
|
||||
assertGt(hyperInflatedHarbPrice, 100_000, "HARB price > $100k is unrealistic");
|
||||
|
||||
// Verify overflow protection holds
|
||||
assertGt(minProductForOverflow, 10**50, "Product threshold astronomically high");
|
||||
assertGt(minProductForOverflow, 10 ** 50, "Product threshold astronomically high");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -420,19 +413,19 @@ contract VWAPTrackerTest is Test {
|
|||
*/
|
||||
function testDoubleOverflowMaximumTransactionScenario() public {
|
||||
// Set up post-compression state (simulate 1000x compression already occurred)
|
||||
uint256 maxSafeValue = type(uint256).max / 10**6;
|
||||
uint256 maxSafeValue = type(uint256).max / 10 ** 6;
|
||||
uint256 compressedValue = maxSafeValue;
|
||||
|
||||
// Manually set post-compression state
|
||||
vm.store(address(vwapTracker), bytes32(uint256(0)), bytes32(compressedValue));
|
||||
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10**30)));
|
||||
vm.store(address(vwapTracker), bytes32(uint256(1)), bytes32(compressedValue / (10 ** 30)));
|
||||
|
||||
// Calculate overflow requirements
|
||||
uint256 availableSpace = type(uint256).max - compressedValue;
|
||||
uint256 minProductForOverflow = availableSpace / 100 + 1;
|
||||
|
||||
// Maximum reasonable transaction scenario: 10,000 ETH (unrealistically large)
|
||||
uint256 maxReasonableFee = 10000 ether;
|
||||
uint256 maxReasonableFee = 10_000 ether;
|
||||
uint256 minPriceForOverflow = minProductForOverflow / maxReasonableFee;
|
||||
|
||||
// Convert to USD equivalent (assuming $3k ETH)
|
||||
|
|
@ -441,13 +434,13 @@ contract VWAPTrackerTest is Test {
|
|||
|
||||
// ASSERTIONS: Verify double overflow requires unrealistic token prices
|
||||
assertGt(minHarbPriceUSD, 1_000_000_000, "Required HARB price > $1B (exceeds global wealth)");
|
||||
assertGt(minPriceForOverflow, 10**30, "Required price X96 astronomically high");
|
||||
assertGt(minPriceForOverflow, 10 ** 30, "Required price X96 astronomically high");
|
||||
|
||||
// Verify transaction size assumption is already unrealistic
|
||||
assertGt(maxReasonableFee, 1000 ether, "10k ETH transaction is unrealistic");
|
||||
|
||||
// Verify the 1000x compression limit provides adequate protection
|
||||
assertGt(minProductForOverflow, 10**50, "Product threshold provides adequate protection");
|
||||
assertGt(minProductForOverflow, 10 ** 50, "Product threshold provides adequate protection");
|
||||
|
||||
// Verify mathematical consistency
|
||||
assertEq(minPriceForOverflow, minProductForOverflow / maxReasonableFee, "Price calculation correct");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../../src/abstracts/PriceOracle.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title PriceOracle Test Suite
|
||||
|
|
@ -58,7 +58,11 @@ contract MockPriceOracle is PriceOracle {
|
|||
int24 centerTick,
|
||||
int24 tickSpacing,
|
||||
bool token0isWeth
|
||||
) external pure returns (bool isUp, bool isEnough) {
|
||||
)
|
||||
external
|
||||
pure
|
||||
returns (bool isUp, bool isEnough)
|
||||
{
|
||||
return _validatePriceMovement(currentTick, centerTick, tickSpacing, token0isWeth);
|
||||
}
|
||||
|
||||
|
|
@ -68,12 +72,12 @@ contract MockPriceOracle is PriceOracle {
|
|||
}
|
||||
|
||||
contract PriceOracleTest is Test {
|
||||
MockPriceOracle priceOracle;
|
||||
MockUniswapV3Pool mockPool;
|
||||
MockPriceOracle internal priceOracle;
|
||||
MockUniswapV3Pool internal mockPool;
|
||||
|
||||
int24 constant TICK_SPACING = 200;
|
||||
uint32 constant PRICE_STABILITY_INTERVAL = 300; // 5 minutes
|
||||
int24 constant MAX_TICK_DEVIATION = 50;
|
||||
int24 internal constant TICK_SPACING = 200;
|
||||
uint32 internal constant PRICE_STABILITY_INTERVAL = 300; // 5 minutes
|
||||
int24 internal constant MAX_TICK_DEVIATION = 50;
|
||||
|
||||
function setUp() public {
|
||||
priceOracle = new MockPriceOracle();
|
||||
|
|
@ -302,8 +306,8 @@ contract PriceOracleTest is Test {
|
|||
|
||||
function testPriceMovementExtremeValues() public {
|
||||
// Test with large but safe tick values to avoid overflow
|
||||
int24 currentTick = 100000;
|
||||
int24 centerTick = -100000;
|
||||
int24 currentTick = 100_000;
|
||||
int24 centerTick = -100_000;
|
||||
bool token0isWeth = true;
|
||||
|
||||
(bool isUp, bool isEnough) = priceOracle.validatePriceMovement(currentTick, centerTick, TICK_SPACING, token0isWeth);
|
||||
|
|
@ -316,15 +320,10 @@ contract PriceOracleTest is Test {
|
|||
// FUZZ TESTS
|
||||
// ========================================
|
||||
|
||||
function testFuzzPriceMovementValidation(
|
||||
int24 currentTick,
|
||||
int24 centerTick,
|
||||
int24 tickSpacing,
|
||||
bool token0isWeth
|
||||
) public {
|
||||
function testFuzzPriceMovementValidation(int24 currentTick, int24 centerTick, int24 tickSpacing, bool token0isWeth) public {
|
||||
// Bound inputs to reasonable ranges
|
||||
currentTick = int24(bound(int256(currentTick), -1000000, 1000000));
|
||||
centerTick = int24(bound(int256(centerTick), -1000000, 1000000));
|
||||
currentTick = int24(bound(int256(currentTick), -1_000_000, 1_000_000));
|
||||
centerTick = int24(bound(int256(centerTick), -1_000_000, 1_000_000));
|
||||
tickSpacing = int24(bound(int256(tickSpacing), 1, 1000));
|
||||
|
||||
(bool isUp, bool isEnough) = priceOracle.validatePriceMovement(currentTick, centerTick, tickSpacing, token0isWeth);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
import "../helpers/TestBase.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title ThreePositionStrategy Test Suite
|
||||
|
|
@ -29,13 +29,7 @@ contract MockThreePositionStrategy is ThreePositionStrategy {
|
|||
|
||||
MintedPosition[] public mintedPositions;
|
||||
|
||||
constructor(
|
||||
address _harbToken,
|
||||
address _wethToken,
|
||||
bool _token0IsWeth,
|
||||
uint256 _ethBalance,
|
||||
uint256 _outstandingSupply
|
||||
) {
|
||||
constructor(address _harbToken, address _wethToken, bool _token0IsWeth, uint256 _ethBalance, uint256 _outstandingSupply) {
|
||||
harbToken = _harbToken;
|
||||
wethToken = _wethToken;
|
||||
token0IsWeth = _token0IsWeth;
|
||||
|
|
@ -75,23 +69,15 @@ contract MockThreePositionStrategy is ThreePositionStrategy {
|
|||
_setPositions(currentTick, params);
|
||||
}
|
||||
|
||||
function setAnchorPosition(int24 currentTick, uint256 anchorEthBalance, PositionParams memory params)
|
||||
external returns (uint256, uint128) {
|
||||
function setAnchorPosition(int24 currentTick, uint256 anchorEthBalance, PositionParams memory params) external returns (uint256, uint128) {
|
||||
return _setAnchorPosition(currentTick, anchorEthBalance, params);
|
||||
}
|
||||
|
||||
function setDiscoveryPosition(int24 currentTick, uint128 anchorLiquidity, PositionParams memory params)
|
||||
external returns (uint256) {
|
||||
function setDiscoveryPosition(int24 currentTick, uint128 anchorLiquidity, PositionParams memory params) external returns (uint256) {
|
||||
return _setDiscoveryPosition(currentTick, anchorLiquidity, params);
|
||||
}
|
||||
|
||||
function setFloorPosition(
|
||||
int24 currentTick,
|
||||
uint256 floorEthBalance,
|
||||
uint256 pulledHarb,
|
||||
uint256 discoveryAmount,
|
||||
PositionParams memory params
|
||||
) external {
|
||||
function setFloorPosition(int24 currentTick, uint256 floorEthBalance, uint256 pulledHarb, uint256 discoveryAmount, PositionParams memory params) external {
|
||||
_setFloorPosition(currentTick, floorEthBalance, pulledHarb, discoveryAmount, params);
|
||||
}
|
||||
|
||||
|
|
@ -109,18 +95,9 @@ contract MockThreePositionStrategy is ThreePositionStrategy {
|
|||
}
|
||||
|
||||
function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal override {
|
||||
positions[stage] = TokenPosition({
|
||||
liquidity: liquidity,
|
||||
tickLower: tickLower,
|
||||
tickUpper: tickUpper
|
||||
});
|
||||
positions[stage] = TokenPosition({ liquidity: liquidity, tickLower: tickLower, tickUpper: tickUpper });
|
||||
|
||||
mintedPositions.push(MintedPosition({
|
||||
stage: stage,
|
||||
tickLower: tickLower,
|
||||
tickUpper: tickUpper,
|
||||
liquidity: liquidity
|
||||
}));
|
||||
mintedPositions.push(MintedPosition({ stage: stage, tickLower: tickLower, tickUpper: tickUpper, liquidity: liquidity }));
|
||||
}
|
||||
|
||||
function _getEthBalance() internal view override returns (uint256) {
|
||||
|
|
@ -133,15 +110,15 @@ contract MockThreePositionStrategy is ThreePositionStrategy {
|
|||
}
|
||||
|
||||
contract ThreePositionStrategyTest is TestConstants {
|
||||
MockThreePositionStrategy strategy;
|
||||
MockThreePositionStrategy internal strategy;
|
||||
|
||||
address constant HARB_TOKEN = address(0x1234);
|
||||
address constant WETH_TOKEN = address(0x5678);
|
||||
address internal constant HARB_TOKEN = address(0x1234);
|
||||
address internal constant WETH_TOKEN = address(0x5678);
|
||||
|
||||
// Default test parameters
|
||||
int24 constant CURRENT_TICK = 0;
|
||||
uint256 constant ETH_BALANCE = 100 ether;
|
||||
uint256 constant OUTSTANDING_SUPPLY = 1000000 ether;
|
||||
int24 internal constant CURRENT_TICK = 0;
|
||||
uint256 internal constant ETH_BALANCE = 100 ether;
|
||||
uint256 internal constant OUTSTANDING_SUPPLY = 1_000_000 ether;
|
||||
|
||||
function setUp() public {
|
||||
strategy = new MockThreePositionStrategy(
|
||||
|
|
@ -163,7 +140,7 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
|
||||
uint256 anchorEthBalance = 20 ether; // 20% of total
|
||||
|
||||
(uint256 pulledHarb, ) = strategy.setAnchorPosition(CURRENT_TICK, anchorEthBalance, params);
|
||||
(uint256 pulledHarb,) = strategy.setAnchorPosition(CURRENT_TICK, anchorEthBalance, params);
|
||||
|
||||
// Verify position was created
|
||||
assertEq(strategy.getMintedPositionsCount(), 1, "Should have minted one position");
|
||||
|
|
@ -189,8 +166,7 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
|
||||
int24 normalizedCurrentTick = CURRENT_TICK / 200 * 200; // Normalize to tick spacing
|
||||
|
||||
assertApproxEqAbs(uint256(int256(centerTick)), uint256(int256(normalizedCurrentTick)), 200,
|
||||
"Anchor should be centered around current tick");
|
||||
assertApproxEqAbs(uint256(int256(centerTick)), uint256(int256(normalizedCurrentTick)), 200, "Anchor should be centered around current tick");
|
||||
}
|
||||
|
||||
function testAnchorPositionWidthScaling() public {
|
||||
|
|
@ -228,9 +204,7 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
int24 anchorSpacing = 200 + (34 * int24(params.anchorWidth) * 200 / 100);
|
||||
int24 anchorWidth = 2 * anchorSpacing;
|
||||
// Adjust for width difference
|
||||
uint128 expectedLiquidity = uint128(
|
||||
uint256(anchorLiquidity) * expectedMultiplier * 11000 / (100 * uint256(int256(anchorWidth)))
|
||||
);
|
||||
uint128 expectedLiquidity = uint128(uint256(anchorLiquidity) * expectedMultiplier * 11_000 / (100 * uint256(int256(anchorWidth))));
|
||||
assertEq(pos.liquidity, expectedLiquidity, "Discovery liquidity should match expected multiple adjusted for width");
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +247,7 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
|
||||
|
||||
// Set up VWAP data
|
||||
uint256 vwapX96 = 79228162514264337593543950336; // 1.0 in X96 format
|
||||
uint256 vwapX96 = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96 format
|
||||
strategy.setVWAP(vwapX96, 1000 ether);
|
||||
|
||||
uint256 floorEthBalance = 80 ether;
|
||||
|
|
@ -294,7 +268,7 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
|
||||
|
||||
// Set up scenario where ETH is insufficient for VWAP price
|
||||
uint256 vwapX96 = 79228162514264337593543950336 * 10; // High VWAP price
|
||||
uint256 vwapX96 = 79_228_162_514_264_337_593_543_950_336 * 10; // High VWAP price
|
||||
strategy.setVWAP(vwapX96, 1000 ether);
|
||||
|
||||
uint256 smallEthBalance = 1 ether; // Insufficient ETH
|
||||
|
|
@ -312,11 +286,11 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
|
||||
|
||||
// Set up scenario where ETH is sufficient for VWAP price
|
||||
uint256 baseVwap = 79228162514264337593543950336; // 1.0 in X96 format
|
||||
uint256 vwapX96 = baseVwap / 100000; // Very low VWAP price to ensure abundance
|
||||
uint256 baseVwap = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96 format
|
||||
uint256 vwapX96 = baseVwap / 100_000; // Very low VWAP price to ensure abundance
|
||||
strategy.setVWAP(vwapX96, 1000 ether);
|
||||
|
||||
uint256 largeEthBalance = 100000 ether; // Very large ETH balance
|
||||
uint256 largeEthBalance = 100_000 ether; // Very large ETH balance
|
||||
uint256 pulledHarb = 1000 ether;
|
||||
uint256 discoveryAmount = 500 ether;
|
||||
|
||||
|
|
@ -346,16 +320,20 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
|
||||
// Expected spacing: TICK_SPACING + (34 * anchorWidth * TICK_SPACING / 100) = 200 + (34 * 50 * 200 / 100) = 3600
|
||||
int24 expectedSpacing = 200 + (34 * 50 * 200 / 100);
|
||||
assertApproxEqAbs(uint256(int256(centerTick)), uint256(int256(CURRENT_TICK + expectedSpacing)), 200,
|
||||
"Floor should be positioned away from current tick to avoid anchor overlap");
|
||||
assertApproxEqAbs(
|
||||
uint256(int256(centerTick)),
|
||||
uint256(int256(CURRENT_TICK + expectedSpacing)),
|
||||
200,
|
||||
"Floor should be positioned away from current tick to avoid anchor overlap"
|
||||
);
|
||||
}
|
||||
|
||||
function testFloorPositionOutstandingSupplyCalculation() public {
|
||||
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
|
||||
|
||||
uint256 initialSupply = 1000000 ether;
|
||||
uint256 pulledHarb = 50000 ether;
|
||||
uint256 discoveryAmount = 30000 ether;
|
||||
uint256 initialSupply = 1_000_000 ether;
|
||||
uint256 pulledHarb = 50_000 ether;
|
||||
uint256 discoveryAmount = 30_000 ether;
|
||||
|
||||
strategy.setOutstandingSupply(initialSupply);
|
||||
|
||||
|
|
@ -462,11 +440,11 @@ contract ThreePositionStrategyTest is TestConstants {
|
|||
function testParameterBounding() public {
|
||||
// Test that large but realistic parameters are handled gracefully
|
||||
ThreePositionStrategy.PositionParams memory extremeParams = ThreePositionStrategy.PositionParams({
|
||||
capitalInefficiency: 10**18, // 100% (maximum reasonable value)
|
||||
anchorShare: 10**18, // 100% (maximum reasonable value)
|
||||
anchorWidth: 1000, // Very wide anchor
|
||||
discoveryDepth: 10**18 // 100% (maximum reasonable value)
|
||||
});
|
||||
capitalInefficiency: 10 ** 18, // 100% (maximum reasonable value)
|
||||
anchorShare: 10 ** 18, // 100% (maximum reasonable value)
|
||||
anchorWidth: 1000, // Very wide anchor
|
||||
discoveryDepth: 10 ** 18 // 100% (maximum reasonable value)
|
||||
});
|
||||
|
||||
// Should not revert even with extreme parameters
|
||||
strategy.setPositions(CURRENT_TICK, extremeParams);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import {TickMath} from "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import {ThreePositionStrategy} from "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
import { ThreePositionStrategy } from "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
|
||||
/**
|
||||
* @title LiquidityBoundaryHelper
|
||||
|
|
@ -15,11 +15,7 @@ library LiquidityBoundaryHelper {
|
|||
/**
|
||||
* @notice Calculates the ETH required to push price to the outer discovery bound
|
||||
*/
|
||||
function calculateBuyLimit(
|
||||
IUniswapV3Pool pool,
|
||||
ThreePositionStrategy liquidityManager,
|
||||
bool token0isWeth
|
||||
) internal view returns (uint256) {
|
||||
function calculateBuyLimit(IUniswapV3Pool pool, ThreePositionStrategy liquidityManager, bool token0isWeth) internal view returns (uint256) {
|
||||
(, int24 currentTick,,,,,) = pool.slot0();
|
||||
|
||||
(uint128 anchorLiquidity, int24 anchorLower, int24 anchorUpper) = liquidityManager.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
|
|
@ -30,36 +26,16 @@ library LiquidityBoundaryHelper {
|
|||
}
|
||||
|
||||
if (token0isWeth) {
|
||||
return _calculateBuyLimitToken0IsWeth(
|
||||
currentTick,
|
||||
anchorLiquidity,
|
||||
anchorLower,
|
||||
anchorUpper,
|
||||
discoveryLiquidity,
|
||||
discoveryLower,
|
||||
discoveryUpper
|
||||
);
|
||||
return _calculateBuyLimitToken0IsWeth(currentTick, anchorLiquidity, anchorLower, anchorUpper, discoveryLiquidity, discoveryLower, discoveryUpper);
|
||||
}
|
||||
|
||||
return _calculateBuyLimitToken1IsWeth(
|
||||
currentTick,
|
||||
anchorLiquidity,
|
||||
anchorLower,
|
||||
anchorUpper,
|
||||
discoveryLiquidity,
|
||||
discoveryLower,
|
||||
discoveryUpper
|
||||
);
|
||||
return _calculateBuyLimitToken1IsWeth(currentTick, anchorLiquidity, anchorLower, anchorUpper, discoveryLiquidity, discoveryLower, discoveryUpper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Calculates the HARB required to push price to the outer floor bound
|
||||
*/
|
||||
function calculateSellLimit(
|
||||
IUniswapV3Pool pool,
|
||||
ThreePositionStrategy liquidityManager,
|
||||
bool token0isWeth
|
||||
) internal view returns (uint256) {
|
||||
function calculateSellLimit(IUniswapV3Pool pool, ThreePositionStrategy liquidityManager, bool token0isWeth) internal view returns (uint256) {
|
||||
(, int24 currentTick,,,,,) = pool.slot0();
|
||||
|
||||
(uint128 anchorLiquidity, int24 anchorLower, int24 anchorUpper) = liquidityManager.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
|
|
@ -70,26 +46,10 @@ library LiquidityBoundaryHelper {
|
|||
}
|
||||
|
||||
if (token0isWeth) {
|
||||
return _calculateSellLimitToken0IsWeth(
|
||||
currentTick,
|
||||
anchorLiquidity,
|
||||
anchorLower,
|
||||
anchorUpper,
|
||||
floorLiquidity,
|
||||
floorLower,
|
||||
floorUpper
|
||||
);
|
||||
return _calculateSellLimitToken0IsWeth(currentTick, anchorLiquidity, anchorLower, anchorUpper, floorLiquidity, floorLower, floorUpper);
|
||||
}
|
||||
|
||||
return _calculateSellLimitToken1IsWeth(
|
||||
currentTick,
|
||||
anchorLiquidity,
|
||||
anchorLower,
|
||||
anchorUpper,
|
||||
floorLiquidity,
|
||||
floorLower,
|
||||
floorUpper
|
||||
);
|
||||
return _calculateSellLimitToken1IsWeth(currentTick, anchorLiquidity, anchorLower, anchorUpper, floorLiquidity, floorLower, floorUpper);
|
||||
}
|
||||
|
||||
function _calculateBuyLimitToken0IsWeth(
|
||||
|
|
@ -100,7 +60,11 @@ library LiquidityBoundaryHelper {
|
|||
uint128 discoveryLiquidity,
|
||||
int24 discoveryLower,
|
||||
int24 discoveryUpper
|
||||
) private pure returns (uint256) {
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (uint256)
|
||||
{
|
||||
if (discoveryLiquidity == 0) {
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
|
@ -135,7 +99,11 @@ library LiquidityBoundaryHelper {
|
|||
uint128 discoveryLiquidity,
|
||||
int24 discoveryLower,
|
||||
int24 discoveryUpper
|
||||
) private pure returns (uint256) {
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (uint256)
|
||||
{
|
||||
if (discoveryLiquidity == 0) {
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
|
@ -170,7 +138,11 @@ library LiquidityBoundaryHelper {
|
|||
uint128 floorLiquidity,
|
||||
int24 floorLower,
|
||||
int24 floorUpper
|
||||
) private pure returns (uint256) {
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (uint256)
|
||||
{
|
||||
if (floorLiquidity == 0) {
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
|
@ -205,7 +177,11 @@ library LiquidityBoundaryHelper {
|
|||
uint128 floorLiquidity,
|
||||
int24 floorLower,
|
||||
int24 floorUpper
|
||||
) private pure returns (uint256) {
|
||||
)
|
||||
private
|
||||
pure
|
||||
returns (uint256)
|
||||
{
|
||||
if (floorLiquidity == 0) {
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
|
||||
import { LiquidityManager } from "../../src/LiquidityManager.sol";
|
||||
|
||||
import "../../src/Optimizer.sol";
|
||||
import { Stake } from "../../src/Stake.sol";
|
||||
import "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
|
||||
import "../../src/helpers/UniswapHelpers.sol";
|
||||
import "../../src/interfaces/IWETH9.sol";
|
||||
|
||||
import "../../test/mocks/MockOptimizer.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import {WETH} from "solmate/tokens/WETH.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "../../src/interfaces/IWETH9.sol";
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {Stake} from "../../src/Stake.sol";
|
||||
import {LiquidityManager} from "../../src/LiquidityManager.sol";
|
||||
import "../../src/helpers/UniswapHelpers.sol";
|
||||
import "../../src/Optimizer.sol";
|
||||
import "../../test/mocks/MockOptimizer.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import { WETH } from "solmate/tokens/WETH.sol";
|
||||
|
||||
// Constants
|
||||
uint24 constant FEE = uint24(10_000); // 1% fee
|
||||
|
|
@ -33,11 +37,11 @@ abstract contract TestConstants is Test {
|
|||
*/
|
||||
function getDefaultParams() internal pure returns (ThreePositionStrategy.PositionParams memory) {
|
||||
return ThreePositionStrategy.PositionParams({
|
||||
capitalInefficiency: 5 * 10 ** 17, // 50%
|
||||
anchorShare: 5 * 10 ** 17, // 50%
|
||||
anchorWidth: 50, // 50%
|
||||
discoveryDepth: 5 * 10 ** 17 // 50%
|
||||
});
|
||||
capitalInefficiency: 5 * 10 ** 17, // 50%
|
||||
anchorShare: 5 * 10 ** 17, // 50%
|
||||
anchorWidth: 50, // 50%
|
||||
discoveryDepth: 5 * 10 ** 17 // 50%
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,16 +101,22 @@ contract TestEnvironment is TestConstants {
|
|||
* @return _optimizer The optimizer contract
|
||||
* @return _token0isWeth Whether token0 is WETH
|
||||
*/
|
||||
function setupEnvironment(bool token0shouldBeWeth, address recenterCaller) external returns (
|
||||
IUniswapV3Factory _factory,
|
||||
IUniswapV3Pool _pool,
|
||||
IWETH9 _weth,
|
||||
Kraiken _harberg,
|
||||
Stake _stake,
|
||||
LiquidityManager _lm,
|
||||
Optimizer _optimizer,
|
||||
bool _token0isWeth
|
||||
) {
|
||||
function setupEnvironment(
|
||||
bool token0shouldBeWeth,
|
||||
address recenterCaller
|
||||
)
|
||||
external
|
||||
returns (
|
||||
IUniswapV3Factory _factory,
|
||||
IUniswapV3Pool _pool,
|
||||
IWETH9 _weth,
|
||||
Kraiken _harberg,
|
||||
Stake _stake,
|
||||
LiquidityManager _lm,
|
||||
Optimizer _optimizer,
|
||||
bool _token0isWeth
|
||||
)
|
||||
{
|
||||
// Deploy factory
|
||||
factory = UniswapHelpers.deployUniswapFactory();
|
||||
|
||||
|
|
@ -208,16 +218,19 @@ contract TestEnvironment is TestConstants {
|
|||
bool token0shouldBeWeth,
|
||||
address recenterCaller,
|
||||
address optimizerAddress
|
||||
) external returns (
|
||||
IUniswapV3Factory _factory,
|
||||
IUniswapV3Pool _pool,
|
||||
IWETH9 _weth,
|
||||
Kraiken _harberg,
|
||||
Stake _stake,
|
||||
LiquidityManager _lm,
|
||||
Optimizer _optimizer,
|
||||
bool _token0isWeth
|
||||
) {
|
||||
)
|
||||
external
|
||||
returns (
|
||||
IUniswapV3Factory _factory,
|
||||
IUniswapV3Pool _pool,
|
||||
IWETH9 _weth,
|
||||
Kraiken _harberg,
|
||||
Stake _stake,
|
||||
LiquidityManager _lm,
|
||||
Optimizer _optimizer,
|
||||
bool _token0isWeth
|
||||
)
|
||||
{
|
||||
// Deploy factory
|
||||
factory = UniswapHelpers.deployUniswapFactory();
|
||||
|
||||
|
|
@ -263,16 +276,19 @@ contract TestEnvironment is TestConstants {
|
|||
bool token0shouldBeWeth,
|
||||
address recenterCaller,
|
||||
address optimizerAddress
|
||||
) external returns (
|
||||
IUniswapV3Factory _factory,
|
||||
IUniswapV3Pool _pool,
|
||||
IWETH9 _weth,
|
||||
Kraiken _harberg,
|
||||
Stake _stake,
|
||||
LiquidityManager _lm,
|
||||
Optimizer _optimizer,
|
||||
bool _token0isWeth
|
||||
) {
|
||||
)
|
||||
external
|
||||
returns (
|
||||
IUniswapV3Factory _factory,
|
||||
IUniswapV3Pool _pool,
|
||||
IWETH9 _weth,
|
||||
Kraiken _harberg,
|
||||
Stake _stake,
|
||||
LiquidityManager _lm,
|
||||
Optimizer _optimizer,
|
||||
bool _token0isWeth
|
||||
)
|
||||
{
|
||||
// Use existing factory
|
||||
factory = existingFactory;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import {TickMath} from "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import {LiquidityAmounts} from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import {SqrtPriceMath} from "@aperture/uni-v3-lib/SqrtPriceMath.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
import { ThreePositionStrategy } from "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
import "../../src/interfaces/IWETH9.sol";
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {ThreePositionStrategy} from "../../src/abstracts/ThreePositionStrategy.sol";
|
||||
import {LiquidityBoundaryHelper} from "./LiquidityBoundaryHelper.sol";
|
||||
import { LiquidityBoundaryHelper } from "./LiquidityBoundaryHelper.sol";
|
||||
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
|
||||
import { SqrtPriceMath } from "@aperture/uni-v3-lib/SqrtPriceMath.sol";
|
||||
import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title UniSwapHelper
|
||||
|
|
@ -120,8 +120,7 @@ abstract contract UniSwapHelper is Test {
|
|||
|
||||
(address seller,, bool isBuy) = abi.decode(_data, (address, uint256, bool));
|
||||
|
||||
(, uint256 amountToPay) =
|
||||
amount0Delta > 0 ? (!token0isWeth, uint256(amount0Delta)) : (token0isWeth, uint256(amount1Delta));
|
||||
(, uint256 amountToPay) = amount0Delta > 0 ? (!token0isWeth, uint256(amount0Delta)) : (token0isWeth, uint256(amount1Delta));
|
||||
if (isBuy) {
|
||||
weth.transfer(msg.sender, amountToPay);
|
||||
} else {
|
||||
|
|
@ -145,7 +144,7 @@ abstract contract UniSwapHelper is Test {
|
|||
// pack ETH
|
||||
uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed;
|
||||
if (weth.balanceOf(address(this)) < ethOwed) {
|
||||
weth.deposit{value: address(this).balance}();
|
||||
weth.deposit{ value: address(this).balance }();
|
||||
}
|
||||
if (ethOwed > 0) {
|
||||
weth.transfer(msg.sender, amount1Owed);
|
||||
|
|
@ -157,7 +156,7 @@ abstract contract UniSwapHelper is Test {
|
|||
// ========================================
|
||||
|
||||
// Safety margin to prevent tick boundary violations (conservative approach)
|
||||
int24 constant TICK_BOUNDARY_SAFETY_MARGIN = 15000;
|
||||
int24 constant TICK_BOUNDARY_SAFETY_MARGIN = 15_000;
|
||||
|
||||
// Price normalization constants
|
||||
uint256 constant NORMALIZATION_HARB_PERCENTAGE = 100; // 1% of HARB balance
|
||||
|
|
@ -189,7 +188,6 @@ abstract contract UniSwapHelper is Test {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Executes a small trade to move price away from tick boundaries
|
||||
* @param moveDown True to move price down (sell HARB), false to move price up (buy HARB)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import "../../src/libraries/UniswapMath.sol";
|
||||
import "@aperture/uni-v3-lib/TickMath.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title UniswapMath Test Suite
|
||||
|
|
@ -29,9 +29,9 @@ contract MockUniswapMath is UniswapMath {
|
|||
}
|
||||
|
||||
contract UniswapMathTest is Test {
|
||||
MockUniswapMath uniswapMath;
|
||||
MockUniswapMath internal uniswapMath;
|
||||
|
||||
int24 constant TICK_SPACING = 200;
|
||||
int24 internal constant TICK_SPACING = 200;
|
||||
|
||||
function setUp() public {
|
||||
uniswapMath = new MockUniswapMath();
|
||||
|
|
@ -75,7 +75,7 @@ contract UniswapMathTest is Test {
|
|||
int24 tick = uniswapMath.tickAtPrice(true, tokenAmount, ethAmount);
|
||||
|
||||
// Should be a large negative tick (cheap token)
|
||||
assertLt(tick, -10000, "Cheap token should result in large negative tick");
|
||||
assertLt(tick, -10_000, "Cheap token should result in large negative tick");
|
||||
assertGt(tick, TickMath.MIN_TICK, "Tick should be within valid range");
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ contract UniswapMathTest is Test {
|
|||
int24 tick = uniswapMath.tickAtPrice(true, tokenAmount, ethAmount);
|
||||
|
||||
// Should be a large positive tick (expensive token)
|
||||
assertGt(tick, 10000, "Expensive token should result in large positive tick");
|
||||
assertGt(tick, 10_000, "Expensive token should result in large positive tick");
|
||||
assertLt(tick, TickMath.MAX_TICK, "Tick should be within valid range");
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ contract UniswapMathTest is Test {
|
|||
|
||||
function testTickPriceRoundTrip() public {
|
||||
// Test that tick → price → tick preserves the original value
|
||||
int24 originalTick = 12345;
|
||||
int24 originalTick = 12_345;
|
||||
originalTick = uniswapMath.clampToTickSpacing(originalTick, TICK_SPACING); // Align to spacing
|
||||
|
||||
uint256 price = uniswapMath.priceAtTick(originalTick);
|
||||
|
|
@ -213,7 +213,7 @@ contract UniswapMathTest is Test {
|
|||
function testFuzzPriceAtTick(int24 tick) public {
|
||||
// Bound tick to reasonable range to avoid extreme prices
|
||||
// Further restrict to prevent overflow in price calculations
|
||||
tick = int24(bound(int256(tick), -200000, 200000));
|
||||
tick = int24(bound(int256(tick), -200_000, 200_000));
|
||||
|
||||
uint256 price = uniswapMath.priceAtTick(tick);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {Stake} from "../../src/Stake.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
import { Stake } from "../../src/Stake.sol";
|
||||
|
||||
contract BearMarketOptimizer {
|
||||
/// @notice Calculate sentiment (not used, but required for interface compatibility)
|
||||
|
|
@ -20,15 +20,11 @@ contract BearMarketOptimizer {
|
|||
/// @return anchorShare 20% - small anchor
|
||||
/// @return anchorWidth 80 - wide width
|
||||
/// @return discoveryDepth 20% - shallow discovery
|
||||
function getLiquidityParams()
|
||||
external
|
||||
pure
|
||||
returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
|
||||
{
|
||||
capitalInefficiency = 8 * 10 ** 17; // 80% - conservative
|
||||
anchorShare = 2 * 10 ** 17; // 20% - small anchor
|
||||
anchorWidth = 1000; // wide width
|
||||
discoveryDepth = 2 * 10 ** 17; // 20% - shallow discovery
|
||||
function getLiquidityParams() external pure returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
capitalInefficiency = 8 * 10 ** 17; // 80% - conservative
|
||||
anchorShare = 2 * 10 ** 17; // 20% - small anchor
|
||||
anchorWidth = 1000; // wide width
|
||||
discoveryDepth = 2 * 10 ** 17; // 20% - shallow discovery
|
||||
}
|
||||
|
||||
function getDescription() external pure returns (string memory) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {Stake} from "../../src/Stake.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
import { Stake } from "../../src/Stake.sol";
|
||||
|
||||
contract BullMarketOptimizer {
|
||||
/// @notice Calculate sentiment (not used, but required for interface compatibility)
|
||||
|
|
@ -20,15 +20,11 @@ contract BullMarketOptimizer {
|
|||
/// @return anchorShare 95% - reduces floor allocation to 90.1%
|
||||
/// @return anchorWidth 50 - medium width for concentrated liquidity
|
||||
/// @return discoveryDepth 1e18 - maximum discovery depth
|
||||
function getLiquidityParams()
|
||||
external
|
||||
pure
|
||||
returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
|
||||
{
|
||||
capitalInefficiency = 0; // 0% - aggressive bull market stance
|
||||
anchorShare = 1e18; // 95% - reduces floor to 90.1% of ETH
|
||||
anchorWidth = 50; // 50% - medium width for concentrated liquidity
|
||||
discoveryDepth = 1e18; // Maximum discovery depth
|
||||
function getLiquidityParams() external pure returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
capitalInefficiency = 0; // 0% - aggressive bull market stance
|
||||
anchorShare = 1e18; // 95% - reduces floor to 90.1% of ETH
|
||||
anchorWidth = 50; // 50% - medium width for concentrated liquidity
|
||||
discoveryDepth = 1e18; // Maximum discovery depth
|
||||
}
|
||||
|
||||
function getDescription() external pure returns (string memory) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,11 @@ contract ExtremeOptimizer {
|
|||
function getOptimalParameters(
|
||||
uint256, // percentageStaked
|
||||
uint256, // avgTaxRate
|
||||
uint256 // sentiment
|
||||
) external returns (uint256, uint256, uint256, uint256) {
|
||||
uint256 // sentiment
|
||||
)
|
||||
external
|
||||
returns (uint256, uint256, uint256, uint256)
|
||||
{
|
||||
callCount++;
|
||||
|
||||
// Cycle through extreme scenarios
|
||||
|
|
@ -22,42 +25,42 @@ contract ExtremeOptimizer {
|
|||
if (scenario == 0) {
|
||||
// Extreme capital inefficiency with minimal anchor
|
||||
return (
|
||||
1e18, // 100% capital inefficiency (KRAIKEN valued at 170%)
|
||||
0.01e18, // 1% anchor share (99% to floor)
|
||||
1, // 1% anchor width (extremely narrow)
|
||||
10e18 // 10x discovery depth
|
||||
1e18, // 100% capital inefficiency (KRAIKEN valued at 170%)
|
||||
0.01e18, // 1% anchor share (99% to floor)
|
||||
1, // 1% anchor width (extremely narrow)
|
||||
10e18 // 10x discovery depth
|
||||
);
|
||||
} else if (scenario == 1) {
|
||||
// Zero capital inefficiency with maximum anchor
|
||||
return (
|
||||
0, // 0% capital inefficiency (KRAIKEN valued at 70%)
|
||||
0.99e18, // 99% anchor share (minimal floor)
|
||||
100, // 100% anchor width (maximum range)
|
||||
0.1e18 // 0.1x discovery depth (minimal discovery)
|
||||
0, // 0% capital inefficiency (KRAIKEN valued at 70%)
|
||||
0.99e18, // 99% anchor share (minimal floor)
|
||||
100, // 100% anchor width (maximum range)
|
||||
0.1e18 // 0.1x discovery depth (minimal discovery)
|
||||
);
|
||||
} else if (scenario == 2) {
|
||||
// Oscillating between extremes
|
||||
return (
|
||||
callCount % 2 == 0 ? 1e18 : 0, // Flip between 0% and 100%
|
||||
0.5e18, // 50% anchor share
|
||||
50, // 50% width
|
||||
callCount % 2 == 0 ? 10e18 : 0.1e18 // Flip discovery depth
|
||||
callCount % 2 == 0 ? 1e18 : 0, // Flip between 0% and 100%
|
||||
0.5e18, // 50% anchor share
|
||||
50, // 50% width
|
||||
callCount % 2 == 0 ? 10e18 : 0.1e18 // Flip discovery depth
|
||||
);
|
||||
} else if (scenario == 3) {
|
||||
// Edge case: Everything at minimum
|
||||
return (
|
||||
0, // Minimum capital inefficiency
|
||||
0, // Minimum anchor share (all to floor)
|
||||
1, // Minimum width
|
||||
0 // No discovery liquidity
|
||||
0, // Minimum capital inefficiency
|
||||
0, // Minimum anchor share (all to floor)
|
||||
1, // Minimum width
|
||||
0 // No discovery liquidity
|
||||
);
|
||||
} else {
|
||||
// Edge case: Everything at maximum
|
||||
return (
|
||||
1e18, // Maximum capital inefficiency
|
||||
1e18, // Maximum anchor share (no floor)
|
||||
100, // Maximum width
|
||||
100e18 // Extreme discovery depth
|
||||
1e18, // Maximum capital inefficiency
|
||||
1e18, // Maximum anchor share (no floor)
|
||||
100, // Maximum width
|
||||
100e18 // Extreme discovery depth
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,48 +12,46 @@ contract MaliciousOptimizer {
|
|||
function getOptimalParameters(
|
||||
uint256, // percentageStaked
|
||||
uint256, // avgTaxRate
|
||||
uint256 // sentiment
|
||||
) external returns (uint256, uint256, uint256, uint256) {
|
||||
uint256 // sentiment
|
||||
)
|
||||
external
|
||||
returns (uint256, uint256, uint256, uint256)
|
||||
{
|
||||
callCount++;
|
||||
|
||||
// Return parameters that should cause problems:
|
||||
// 1. First call: All liquidity in floor (no anchor protection)
|
||||
if (callCount == 1) {
|
||||
return (
|
||||
0, // 0% capital inefficiency (minimum KRAIKEN value)
|
||||
0, // 0% anchor share (100% to floor)
|
||||
1, // Minimal width
|
||||
0 // No discovery
|
||||
0, // 0% capital inefficiency (minimum KRAIKEN value)
|
||||
0, // 0% anchor share (100% to floor)
|
||||
1, // Minimal width
|
||||
0 // No discovery
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Second call: Suddenly switch to all anchor (no floor protection)
|
||||
if (callCount == 2) {
|
||||
return (
|
||||
1e18, // 100% capital inefficiency (maximum KRAIKEN value)
|
||||
1e18, // 100% anchor share (0% to floor)
|
||||
100, // Maximum width
|
||||
0 // No discovery
|
||||
1e18, // 100% capital inefficiency (maximum KRAIKEN value)
|
||||
1e18, // 100% anchor share (0% to floor)
|
||||
100, // Maximum width
|
||||
0 // No discovery
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Third call: Create huge discovery position
|
||||
if (callCount == 3) {
|
||||
return (
|
||||
0.5e18, // 50% capital inefficiency
|
||||
0.1e18, // 10% anchor share
|
||||
10, // Small width
|
||||
100e18 // Massive discovery depth
|
||||
0.5e18, // 50% capital inefficiency
|
||||
0.1e18, // 10% anchor share
|
||||
10, // Small width
|
||||
100e18 // Massive discovery depth
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Oscillate wildly
|
||||
return (
|
||||
callCount % 2 == 0 ? 0 : 1e18,
|
||||
callCount % 2 == 0 ? 0 : 1e18,
|
||||
callCount % 2 == 0 ? 1 : 100,
|
||||
callCount % 2 == 0 ? 0 : 10e18
|
||||
);
|
||||
return (callCount % 2 == 0 ? 0 : 1e18, callCount % 2 == 0 ? 0 : 1e18, callCount % 2 == 0 ? 1 : 100, callCount % 2 == 0 ? 0 : 10e18);
|
||||
}
|
||||
|
||||
function calculateSentiment(uint256, uint256) public pure returns (uint256) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ contract MockKraiken {
|
|||
uint8 public constant decimals = 18;
|
||||
|
||||
function totalSupply() external pure returns (uint256) {
|
||||
return 1000000 * 10**18; // 1M tokens
|
||||
return 1_000_000 * 10 ** 18; // 1M tokens
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {Stake} from "../../src/Stake.sol";
|
||||
import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
import { Stake } from "../../src/Stake.sol";
|
||||
|
||||
import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol";
|
||||
import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||||
|
||||
contract MockOptimizer is Initializable, UUPSUpgradeable {
|
||||
Kraiken internal kraiken;
|
||||
|
|
@ -44,19 +45,14 @@ contract MockOptimizer is Initializable, UUPSUpgradeable {
|
|||
}
|
||||
}
|
||||
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin {}
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { }
|
||||
|
||||
/// @notice Set liquidity parameters for sentiment analysis testing
|
||||
/// @param capitalInefficiency Capital inefficiency parameter (0-1e18)
|
||||
/// @param anchorShare Anchor share parameter (0-1e18)
|
||||
/// @param anchorWidth Anchor width parameter
|
||||
/// @param discoveryDepth Discovery depth parameter (0-1e18)
|
||||
function setLiquidityParams(
|
||||
uint256 capitalInefficiency,
|
||||
uint256 anchorShare,
|
||||
uint24 anchorWidth,
|
||||
uint256 discoveryDepth
|
||||
) external {
|
||||
function setLiquidityParams(uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) external {
|
||||
_capitalInefficiency = capitalInefficiency;
|
||||
_anchorShare = anchorShare;
|
||||
_anchorWidth = anchorWidth;
|
||||
|
|
@ -80,11 +76,7 @@ contract MockOptimizer is Initializable, UUPSUpgradeable {
|
|||
/// @return anchorShare Configurable anchor share
|
||||
/// @return anchorWidth Configurable anchor width
|
||||
/// @return discoveryDepth Configurable discovery depth
|
||||
function getLiquidityParams()
|
||||
external
|
||||
view
|
||||
returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
|
||||
{
|
||||
function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
capitalInefficiency = _capitalInefficiency;
|
||||
anchorShare = _anchorShare;
|
||||
anchorWidth = _anchorWidth;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {Stake} from "../../src/Stake.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
import { Stake } from "../../src/Stake.sol";
|
||||
|
||||
contract NeutralMarketOptimizer {
|
||||
/// @notice Calculate sentiment (not used, but required for interface compatibility)
|
||||
|
|
@ -20,15 +20,11 @@ contract NeutralMarketOptimizer {
|
|||
/// @return anchorShare 50% - balanced anchor
|
||||
/// @return anchorWidth 50 - standard width
|
||||
/// @return discoveryDepth 50% - balanced discovery
|
||||
function getLiquidityParams()
|
||||
external
|
||||
pure
|
||||
returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
|
||||
{
|
||||
capitalInefficiency = 5 * 10 ** 17; // 50% - balanced
|
||||
anchorShare = 5 * 10 ** 17; // 50% - balanced anchor
|
||||
anchorWidth = 1000; // standard width
|
||||
discoveryDepth = 5 * 10 ** 17; // 50% - balanced discovery
|
||||
function getLiquidityParams() external pure returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||||
capitalInefficiency = 5 * 10 ** 17; // 50% - balanced
|
||||
anchorShare = 5 * 10 ** 17; // 50% - balanced anchor
|
||||
anchorWidth = 1000; // standard width
|
||||
discoveryDepth = 5 * 10 ** 17; // 50% - balanced discovery
|
||||
}
|
||||
|
||||
function getDescription() external pure returns (string memory) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ contract RandomScenarioOptimizer is MockOptimizer {
|
|||
uint24 anchorWidth,
|
||||
uint256 discoveryDepth,
|
||||
string memory scenarioDescription
|
||||
) external {
|
||||
)
|
||||
external
|
||||
{
|
||||
_capitalInefficiency = capitalInefficiency;
|
||||
_anchorShare = anchorShare;
|
||||
_anchorWidth = anchorWidth;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Kraiken} from "../../src/Kraiken.sol";
|
||||
import {Stake} from "../../src/Stake.sol";
|
||||
import { Kraiken } from "../../src/Kraiken.sol";
|
||||
import { Stake } from "../../src/Stake.sol";
|
||||
|
||||
/**
|
||||
* @title WhaleOptimizer
|
||||
|
|
@ -20,10 +20,10 @@ contract WhaleOptimizer {
|
|||
|
||||
function getLiquidityParams() external pure returns (uint256, uint256, uint24, uint256) {
|
||||
return (
|
||||
1e17, // capitalInefficiency: 10% (very aggressive)
|
||||
1e17, // capitalInefficiency: 10% (very aggressive)
|
||||
95e16, // anchorShare: 95% (massive anchor position)
|
||||
10, // anchorWidth: 10 (extremely narrow)
|
||||
5e16 // discoveryDepth: 5% (minimal discovery)
|
||||
10, // anchorWidth: 10 (extremely narrow)
|
||||
5e16 // discoveryDepth: 5% (minimal discovery)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue