Merge pull request 'fix: No events on fee destination state changes (#958)' (#982) from fix/issue-958 into master

This commit is contained in:
johba 2026-03-18 23:33:57 +01:00
commit bc2afefcbe
3 changed files with 41 additions and 3 deletions

View file

@ -25,6 +25,10 @@ export const STACK_META_ID = 'stack-meta';
* Version History: * Version History:
* - v1: Initial deployment (30-tier TAX_RATES, index-based staking) * - v1: Initial deployment (30-tier TAX_RATES, index-based staking)
* - v2: OptimizerV3, VWAP mirror floor, directional VWAP recording * - 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]; export const COMPATIBLE_CONTRACT_VERSIONS = [1, 2];

View file

@ -65,6 +65,12 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
/// @notice Emitted on each successful recenter for monitoring and indexing /// @notice Emitted on each successful recenter for monitoring and indexing
event Recentered(int24 indexed currentTick, bool indexed isUp); 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 /// @notice Custom errors
error ZeroAddressInSetter(); error ZeroAddressInSetter();
@ -139,12 +145,15 @@ contract LiquidityManager is ThreePositionStrategy, PriceOracle {
// to storage. A subsequent SELFDESTRUCT clears the bytecode but cannot undo this write. // to storage. A subsequent SELFDESTRUCT clears the bytecode but cannot undo this write.
if (!feeDestinationLocked && feeDestination != address(0) && feeDestination.code.length > 0) { if (!feeDestinationLocked && feeDestination != address(0) && feeDestination.code.length > 0) {
feeDestinationLocked = true; feeDestinationLocked = true;
emit FeeDestinationLocked(feeDestination);
return; return;
} }
require(!feeDestinationLocked, "fee destination locked"); require(!feeDestinationLocked, "fee destination locked");
feeDestination = feeDestination_; feeDestination = feeDestination_;
emit FeeDestinationSet(feeDestination_);
if (feeDestination_.code.length > 0) { if (feeDestination_.code.length > 0) {
feeDestinationLocked = true; feeDestinationLocked = true;
emit FeeDestinationLocked(feeDestination_);
} }
} }

View file

@ -979,9 +979,15 @@ contract LiquidityManagerTest is UniSwapHelper {
*/ */
function testSetFeeDestinationEOA_MultipleAllowed() public { function testSetFeeDestinationEOA_MultipleAllowed() public {
LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); 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"); 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"); assertFalse(freshLm.feeDestinationLocked(), "should still not be locked after second EOA set");
} }
@ -991,6 +997,10 @@ contract LiquidityManagerTest is UniSwapHelper {
function testSetFeeDestinationContract_Locks() public { function testSetFeeDestinationContract_Locks() public {
LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer));
// address(harberg) is a deployed contract // 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)); freshLm.setFeeDestination(address(harberg));
assertTrue(freshLm.feeDestinationLocked(), "should be locked after contract set"); assertTrue(freshLm.feeDestinationLocked(), "should be locked after contract set");
assertEq(freshLm.feeDestination(), address(harberg)); assertEq(freshLm.feeDestination(), address(harberg));
@ -1001,10 +1011,17 @@ contract LiquidityManagerTest is UniSwapHelper {
*/ */
function testSetFeeDestinationEOAToContract_Locks() public { function testSetFeeDestinationEOAToContract_Locks() public {
LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); 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 // 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"); assertFalse(freshLm.feeDestinationLocked(), "not locked after EOA set");
// Step 2: upgrade to treasury contract once it is deployed locks permanently // 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)); freshLm.setFeeDestination(address(harberg));
assertTrue(freshLm.feeDestinationLocked(), "locked after contract set"); assertTrue(freshLm.feeDestinationLocked(), "locked after contract set");
assertEq(freshLm.feeDestination(), address(harberg)); assertEq(freshLm.feeDestination(), address(harberg));
@ -1018,6 +1035,10 @@ contract LiquidityManagerTest is UniSwapHelper {
*/ */
function testSetFeeDestinationLocked_Reverts() public { function testSetFeeDestinationLocked_Reverts() public {
LiquidityManager freshLm = new LiquidityManager(address(factory), address(weth), address(harberg), address(optimizer)); 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)); freshLm.setFeeDestination(address(harberg));
vm.expectRevert("fee destination locked"); vm.expectRevert("fee destination locked");
freshLm.setFeeDestination(makeAddr("anyAddr")); freshLm.setFeeDestination(makeAddr("anyAddr"));
@ -1033,6 +1054,8 @@ contract LiquidityManagerTest is UniSwapHelper {
address eoaAddr = makeAddr("precomputedEOA"); address eoaAddr = makeAddr("precomputedEOA");
// Step 1: set to EOA allowed, lock stays false // Step 1: set to EOA allowed, lock stays false
vm.expectEmit(true, false, false, false, address(freshLm));
emit LiquidityManager.FeeDestinationSet(eoaAddr);
freshLm.setFeeDestination(eoaAddr); freshLm.setFeeDestination(eoaAddr);
assertFalse(freshLm.feeDestinationLocked(), "not locked after EOA set"); 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 // Step 3: calling setFeeDestination detects bytecode at the current destination and
// commits feeDestinationLocked = true WITHOUT reverting, so the write survives // commits feeDestinationLocked = true WITHOUT reverting, so the write survives
// a later SELFDESTRUCT. feeDestination itself is not changed. // a later SELFDESTRUCT. feeDestination itself is not changed.
vm.expectEmit(true, false, false, false, address(freshLm));
emit LiquidityManager.FeeDestinationLocked(eoaAddr);
freshLm.setFeeDestination(makeAddr("attacker")); freshLm.setFeeDestination(makeAddr("attacker"));
assertTrue(freshLm.feeDestinationLocked(), "locked after bytecode detected at current feeDestination"); assertTrue(freshLm.feeDestinationLocked(), "locked after bytecode detected at current feeDestination");
assertEq(freshLm.feeDestination(), eoaAddr, "feeDestination must not change when defensive lock triggers"); assertEq(freshLm.feeDestination(), eoaAddr, "feeDestination must not change when defensive lock triggers");