diff --git a/kraiken-lib/src/version.ts b/kraiken-lib/src/version.ts index 33763a5..5ffa71d 100644 --- a/kraiken-lib/src/version.ts +++ b/kraiken-lib/src/version.ts @@ -25,6 +25,10 @@ export const STACK_META_ID = 'stack-meta'; * Version History: * - v1: Initial deployment (30-tier TAX_RATES, index-based staking) * - v2: OptimizerV3, VWAP mirror floor, directional VWAP recording + * + * LiquidityManager event additions (no Kraiken VERSION bump): + * - FeeDestinationSet(address indexed newDest) — emitted on every setFeeDestination() assignment + * - FeeDestinationLocked(address indexed dest) — emitted when the fee destination lock engages */ export const COMPATIBLE_CONTRACT_VERSIONS = [1, 2]; diff --git a/onchain/src/LiquidityManager.sol b/onchain/src/LiquidityManager.sol index 34cae67..92ce730 100644 --- a/onchain/src/LiquidityManager.sol +++ b/onchain/src/LiquidityManager.sol @@ -65,6 +65,12 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle { /// @notice Emitted on each successful recenter for monitoring and indexing event Recentered(int24 indexed currentTick, bool indexed isUp); + /// @notice Emitted whenever feeDestination is updated + event FeeDestinationSet(address indexed newDest); + + /// @notice Emitted when the fee destination lock is permanently engaged + event FeeDestinationLocked(address indexed dest); + /// @notice Custom errors error ZeroAddressInSetter(); @@ -139,12 +145,15 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle { // to storage. A subsequent SELFDESTRUCT clears the bytecode but cannot undo this write. if (!feeDestinationLocked && feeDestination != address(0) && feeDestination.code.length > 0) { feeDestinationLocked = true; + emit FeeDestinationLocked(feeDestination); return; } require(!feeDestinationLocked, "fee destination locked"); feeDestination = feeDestination_; + emit FeeDestinationSet(feeDestination_); if (feeDestination_.code.length > 0) { feeDestinationLocked = true; + emit FeeDestinationLocked(feeDestination_); } } diff --git a/onchain/test/LiquidityManager.t.sol b/onchain/test/LiquidityManager.t.sol index 8c75a79..b092f14 100644 --- a/onchain/test/LiquidityManager.t.sol +++ b/onchain/test/LiquidityManager.t.sol @@ -979,9 +979,15 @@ contract LiquidityManagerTest is UniSwapHelper { */ function testSetFeeDestinationEOA_MultipleAllowed() public { LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); - freshLm.setFeeDestination(makeAddr("firstFee")); + address firstFee = makeAddr("firstFee"); + address secondFee = makeAddr("secondFee"); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(firstFee); + freshLm.setFeeDestination(firstFee); assertFalse(freshLm.feeDestinationLocked(), "should not be locked after EOA set"); - freshLm.setFeeDestination(makeAddr("secondFee")); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(secondFee); + freshLm.setFeeDestination(secondFee); assertFalse(freshLm.feeDestinationLocked(), "should still not be locked after second EOA set"); } @@ -991,6 +997,10 @@ contract LiquidityManagerTest is UniSwapHelper { function testSetFeeDestinationContract_Locks() public { LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); // address(harberg) is a deployed contract + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(address(harberg)); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationLocked(address(harberg)); freshLm.setFeeDestination(address(harberg)); assertTrue(freshLm.feeDestinationLocked(), "should be locked after contract set"); assertEq(freshLm.feeDestination(), address(harberg)); @@ -1001,10 +1011,17 @@ contract LiquidityManagerTest is UniSwapHelper { */ function testSetFeeDestinationEOAToContract_Locks() public { LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); + address treasuryEOA = makeAddr("treasuryEOA"); // Step 1: set to an EOA during setup — allowed, not locked - freshLm.setFeeDestination(makeAddr("treasuryEOA")); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(treasuryEOA); + freshLm.setFeeDestination(treasuryEOA); assertFalse(freshLm.feeDestinationLocked(), "not locked after EOA set"); // Step 2: upgrade to treasury contract once it is deployed — locks permanently + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(address(harberg)); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationLocked(address(harberg)); freshLm.setFeeDestination(address(harberg)); assertTrue(freshLm.feeDestinationLocked(), "locked after contract set"); assertEq(freshLm.feeDestination(), address(harberg)); @@ -1018,6 +1035,10 @@ contract LiquidityManagerTest is UniSwapHelper { */ function testSetFeeDestinationLocked_Reverts() public { LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(address(harberg)); + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationLocked(address(harberg)); freshLm.setFeeDestination(address(harberg)); vm.expectRevert("fee destination locked"); freshLm.setFeeDestination(makeAddr("anyAddr")); @@ -1033,6 +1054,8 @@ contract LiquidityManagerTest is UniSwapHelper { address eoaAddr = makeAddr("precomputedEOA"); // Step 1: set to EOA — allowed, lock stays false + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationSet(eoaAddr); freshLm.setFeeDestination(eoaAddr); assertFalse(freshLm.feeDestinationLocked(), "not locked after EOA set"); @@ -1043,6 +1066,8 @@ contract LiquidityManagerTest is UniSwapHelper { // Step 3: calling setFeeDestination detects bytecode at the current destination and // commits feeDestinationLocked = true WITHOUT reverting, so the write survives // a later SELFDESTRUCT. feeDestination itself is not changed. + vm.expectEmit(true, false, false, false, address(freshLm)); + emit LiquidityManager.FeeDestinationLocked(eoaAddr); freshLm.setFeeDestination(makeAddr("attacker")); assertTrue(freshLm.feeDestinationLocked(), "locked after bytecode detected at current feeDestination"); assertEq(freshLm.feeDestination(), eoaAddr, "feeDestination must not change when defensive lock triggers");