Skip to content

feat(contracts): BellStrategy.computePositions (#32)#172

Merged
ozpool merged 2 commits into
contracts/20-istrategyfrom
contracts/32-bell-compute
May 4, 2026
Merged

feat(contracts): BellStrategy.computePositions (#32)#172
ozpool merged 2 commits into
contracts/20-istrategyfrom
contracts/32-bell-compute

Conversation

@ozpool
Copy link
Copy Markdown
Owner

@ozpool ozpool commented Apr 29, 2026

Summary

Default PRISM strategy. Returns 7 positions arranged as a symmetric bell around the current tick, weights `[500, 1200, 2100, 2400, 2100, 1200, 500]` bps (Σ = 10_000).

Purity contract (ADR-005)

  • No SSTORE in runtime bytecode — weights are inline literal constants
  • Deterministic across repeat calls with identical inputs
  • amounts0 / amounts1 are opaque to the strategy (vault-agnosticism)

Tick alignment

`currentTick` is floored to the nearest tickSpacing boundary (negative-tick aware via signed-mod handling). The 7 positions tile contiguously across `[anchor - 3ts, anchor + 4ts]` with each position spanning exactly one tickSpacing.

shouldRebalance stub

Returns false on this PR. The actual gate (tick drift + time threshold + 24h fallback) lands in #33.

Quality gates

  • `forge build`: pass
  • `forge fmt`: clean
  • `forge test test/strategies/BellStrategy.t.sol`: 16/16 pass

Test plan

  • 7 positions, weights sum to 10_000, symmetric, centre largest
  • zero / negative tickSpacing reverts `InvalidTickRange`
  • anchor floors at zero, positive, and negative ticks
  • adjacent positions contiguous; width == spacing
  • fuzz: deterministic across repeat calls
  • fuzz: weight invariant holds for any (tick, spacing)
  • fuzz: amounts are opaque (vault-agnosticism)

Dependencies

Closes #32

ozpool added 2 commits April 29, 2026 15:06
Adds packages/contracts/src/utils/Errors.sol — every PRISM contract
reverts through a 4-byte selector (~200 gas) instead of a string
(2,000+ gas). Errors are grouped by domain (access control, vault,
strategy, hook, oracle, callback, generic) with NatSpec describing
the call site of each.

Coverage matches PRD §Day 1 plus the three errors introduced by
ADR-004 (UnknownOp, DeltaUnsettled, Reentrancy) and the bound/value
helpers (MathOverflow, ValueOutOfBounds) used by FeeLib (#23) and
PositionLib (#22).

Tests in test/utils/Errors.t.sol snapshot every selector against its
canonical signature — 21 selectors plus 4 round-trip reverts. Selector
stability is a downstream-consumer contract; bumping a signature is a
breaking change and the test will catch it.

forge build clean, forge test 27/27 pass, forge fmt --check clean.

Closes #17. Blocks #26, #27, #28, #29, #32, #34, #35, #36, #37.
)

Default PRISM strategy. Returns 7 positions arranged as a symmetric
bell around the current tick, with hardcoded weights
[500, 1200, 2100, 2400, 2100, 1200, 500] bps (Σ = 10_000).

Per ADR-005 the strategy is pure / stateless / vault-agnostic:
- No SSTORE in runtime bytecode (constants only — W0..W6 are inline
  compile-time literals; CI bytecode scan in #61 verifies this)
- Deterministic across repeat calls with identical inputs
- amounts0/amounts1 are opaque to the strategy (vault-agnosticism)

Tick alignment: currentTick is floored to the nearest tickSpacing
boundary (negative-tick aware). The 7 positions tile contiguously
across [anchor - 3*ts, anchor + 4*ts] with each position spanning
exactly one tickSpacing.

shouldRebalance returns false on this PR — #33 implements the actual
gate (drift + time + 24h fallback).

16 tests:
- 7 unit tests for shape, symmetry, sum, alignment, contiguity,
  width, anchor centering
- 2 revert tests for zero / negative spacing
- 3 fuzz tests for determinism, weight invariant, vault-agnosticism
- 4 supporting tests for constants and ctor

Stacked on contracts/20-istrategy with cherry-picked Errors (#17).

Closes #32
@ozpool ozpool merged commit fc8185b into contracts/20-istrategy May 4, 2026
ozpool added a commit that referenced this pull request May 4, 2026
* feat(contracts): IStrategy interface (#20)

packages/contracts/src/interfaces/IStrategy.sol — pluggable rebalance
shape API. Strategies decide tick-range positions and rebalance
timing; vault remains the trust boundary.

Surface matches PRD §Day 3: TargetPosition struct + computePositions +
shouldRebalance. NatSpec captures the ADR-005 purity contract:
deterministic, stateless, vault-agnostic; block.timestamp permitted
only inside shouldRebalance for the 24h liveness fallback; runtime
SSTORE forbidden (CI bytecode scan).

The vault re-checks Σ weight == 10_000 (#2) and length <= MAX_POSITIONS
(#3) after every call, so the interface itself doesn't need to police
malformed strategies — the vault reverts via Errors.WeightsDoNotSum or
Errors.MaxPositionsExceeded.

Tests: 2 selectors snapshotted, mock returns a full-range single
position to exercise the round-trip, fuzz over inputs verifies the
weight-sum invariant on the mock. 4/4 pass.

forge build clean, forge fmt --check clean.

Closes #20. Blocks #32, #33, #39.

* feat(contracts): BellStrategy.computePositions (#32) (#172)

* feat(contracts): centralised custom error library (#17)

Adds packages/contracts/src/utils/Errors.sol — every PRISM contract
reverts through a 4-byte selector (~200 gas) instead of a string
(2,000+ gas). Errors are grouped by domain (access control, vault,
strategy, hook, oracle, callback, generic) with NatSpec describing
the call site of each.

Coverage matches PRD §Day 1 plus the three errors introduced by
ADR-004 (UnknownOp, DeltaUnsettled, Reentrancy) and the bound/value
helpers (MathOverflow, ValueOutOfBounds) used by FeeLib (#23) and
PositionLib (#22).

Tests in test/utils/Errors.t.sol snapshot every selector against its
canonical signature — 21 selectors plus 4 round-trip reverts. Selector
stability is a downstream-consumer contract; bumping a signature is a
breaking change and the test will catch it.

forge build clean, forge test 27/27 pass, forge fmt --check clean.

Closes #17. Blocks #26, #27, #28, #29, #32, #34, #35, #36, #37.

* feat(contracts): BellStrategy.computePositions — bell-curve weights (#32)

Default PRISM strategy. Returns 7 positions arranged as a symmetric
bell around the current tick, with hardcoded weights
[500, 1200, 2100, 2400, 2100, 1200, 500] bps (Σ = 10_000).

Per ADR-005 the strategy is pure / stateless / vault-agnostic:
- No SSTORE in runtime bytecode (constants only — W0..W6 are inline
  compile-time literals; CI bytecode scan in #61 verifies this)
- Deterministic across repeat calls with identical inputs
- amounts0/amounts1 are opaque to the strategy (vault-agnosticism)

Tick alignment: currentTick is floored to the nearest tickSpacing
boundary (negative-tick aware). The 7 positions tile contiguously
across [anchor - 3*ts, anchor + 4*ts] with each position spanning
exactly one tickSpacing.

shouldRebalance returns false on this PR — #33 implements the actual
gate (drift + time + 24h fallback).

16 tests:
- 7 unit tests for shape, symmetry, sum, alignment, contiguity,
  width, anchor centering
- 2 revert tests for zero / negative spacing
- 3 fuzz tests for determinism, weight invariant, vault-agnosticism
- 4 supporting tests for constants and ctor

Stacked on contracts/20-istrategy with cherry-picked Errors (#17).

Closes #32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant