Skip to content

Commit 53c566b

Browse files
committed
feat: Verify activeId can not be zero in bin pool
1 parent 7c04695 commit 53c566b

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

test/pool-bin/BinPoolManager.t.sol

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {PriceHelper} from "../../src/pool-bin/libraries/PriceHelper.sol";
3939
import {BinHelper} from "../../src/pool-bin/libraries/BinHelper.sol";
4040
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
4141
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
42+
import {Uint128x128Math} from "../../src/pool-bin/libraries/math/Uint128x128Math.sol";
4243

4344
contract BinPoolManagerTest is Test, BinTestHelper {
4445
using SafeCast for uint256;
@@ -305,6 +306,87 @@ contract BinPoolManagerTest is Test, BinTestHelper {
305306
poolManager.initialize(key, 0);
306307
}
307308

309+
// -----------------------------------------------------------------------
310+
// activeId = 0 analysis: implicit defense in BinPoolManager.initialize
311+
// See: docs/activeId-price-relationship.md §9
312+
// -----------------------------------------------------------------------
313+
314+
/// @notice When activeId=0, pow() inside getPriceFromId reverts because the
315+
/// absolute value of the exponent (8,388,608) exceeds the 2^20 limit,
316+
/// throwing Uint128x128Math__PowUnderflow rather than an explicit
317+
/// input-validation error. The guard is a math side-effect, not an
318+
/// intentional parameter check.
319+
function test_InitializeActiveIdZero_RevertsWithPowUnderflow() public {
320+
// base = 1 + binStep/10000 expressed as a 128.128 fixed-point number
321+
// binStep = 10 (set in setUp via poolParam.setBinStep(10))
322+
uint256 base = Constants.SCALE + (uint256(10) << Constants.SCALE_OFFSET) / Constants.BASIS_POINT_MAX;
323+
// exponent = 0 - REAL_ID_SHIFT = -8,388,608
324+
int256 exponent = -int256(uint256(1 << 23));
325+
326+
vm.expectRevert(abi.encodeWithSelector(Uint128x128Math.Uint128x128Math__PowUnderflow.selector, base, exponent));
327+
poolManager.initialize(key, 0);
328+
}
329+
330+
/// @notice After initialize(key, 0) reverts, the pool slot0 remains all-zero
331+
/// (never written). getSlot0 returning activeId=0 means "never initialized",
332+
/// not "initialized with id 0". These two states are indistinguishable
333+
/// on-chain, which is the root design flaw.
334+
function test_InitializeActiveIdZero_PoolStateUnchanged() public {
335+
// Attempt to initialize with activeId=0; expect revert
336+
vm.expectRevert();
337+
poolManager.initialize(key, 0);
338+
339+
// Pool was never written: slot0 is still all-zero
340+
(uint24 storedActiveId,,) = poolManager.getSlot0(key.toId());
341+
assertEq(storedActiveId, 0, "pool should remain uninitialized (slot0 = 0)");
342+
343+
// A valid activeId on the same key initializes successfully
344+
uint24 validId = 2 ** 23; // price == 1
345+
poolManager.initialize(key, validId);
346+
(storedActiveId,,) = poolManager.getSlot0(key.toId());
347+
assertEq(storedActiveId, validId, "pool should be initialized with valid activeId");
348+
}
349+
350+
/// @notice The usable activeId range is bounded by fixed-point precision of
351+
/// base^exponent, not merely by the |exponent| < 2^20 gate in pow().
352+
/// For binStep=10 (base≈1.001), pow() underflows to zero at |exponent|≈88,767.
353+
/// activeId=0 has exponent=-8,388,608, roughly 94x beyond that practical limit.
354+
function test_InitializeActiveIdZero_BoundaryComparison() public {
355+
uint24 REAL_ID_SHIFT = uint24(1 << 23); // 8,388,608 — the price-equals-1 anchor
356+
357+
// --- Practical minimum valid activeId for binStep=10 (offset -88,767) ---
358+
// Confirmed by test_fuzz_ValidPrice; asserted here for explicitness
359+
uint24 practicalMinValid = REAL_ID_SHIFT - 88_767; // 8,299,841
360+
poolManager.initialize(key, practicalMinValid);
361+
(uint24 storedId,,) = poolManager.getSlot0(key.toId());
362+
assertEq(storedId, practicalMinValid, "practicalMinValid should initialize successfully");
363+
364+
// --- One step beyond the practical boundary: pow precision underflow, revert ---
365+
PoolKey memory key2 = PoolKey({
366+
currency0: currency0,
367+
currency1: currency1,
368+
hooks: IHooks(address(0)),
369+
poolManager: IPoolManager(address(poolManager)),
370+
fee: uint24(500),
371+
parameters: poolParam.setBinStep(10)
372+
});
373+
uint24 practicalMinInvalid = REAL_ID_SHIFT - 88_768; // 8,299,840
374+
vm.expectRevert();
375+
poolManager.initialize(key2, practicalMinInvalid);
376+
377+
// --- activeId=0: exponent=-8,388,608, ~94x beyond the practical limit, revert ---
378+
PoolKey memory key3 = PoolKey({
379+
currency0: currency0,
380+
currency1: currency1,
381+
hooks: IHooks(address(0)),
382+
poolManager: IPoolManager(address(poolManager)),
383+
fee: uint24(100),
384+
parameters: poolParam.setBinStep(10)
385+
});
386+
vm.expectRevert();
387+
poolManager.initialize(key3, 0);
388+
}
389+
308390
function testInitializeSwapFeeTooLarge() public {
309391
uint24 swapFee = LPFeeLibrary.TEN_PERCENT_FEE + 1;
310392

0 commit comments

Comments
 (0)