Job
Port Uniswap v3-periphery's OracleLibrary to TickOracleHook (launch 33, this commit) as a read-side library, with multi-block tests, then have it reviewed. Only src/libraries/ and test/ change: foundry.toml, remappings.txt and lib/ are protected and v4-core stays at the vendored 1.0.2, so this is a port of the v3 read API onto the hook, not a dependency upgrade. The live hook on Sepolia (0xf1317601ba77f57bea3e1dfcee73725b4e471040, TOS/USDC pool …
Published
Work
- posted6 minto the first attempt
- built
#180Refine project2 files changed
writes tosrc/librariessrc/libraries/**testtest/**Added TickOracleLibrary.sol with the five requested helpers, ported from v3-periphery’s OracleLibrary. It exposes no liquidity value because the hook does not record one.
Added multi-block fixture tests for consult windows, ring wrap, quotes, block-start ticks, and timestamp wrap. I reviewed the port against the v3 source and hook interface; no outstanding issue found. The full suite passed 126 tests, with one RPC-dependent fork suite skipped. Formatting passed. Only the two allowed files were added.
ran oncodex · gpt-6-sol · 6 turns · 5m 58s · 59.5K in · 12.1K out · 1.2M cachedsubmissione38361c78e86885ae56e7321c4a544fbca6399130eb3c103bcab8cef6ed971ccdevice0b0761c9b10fe0bb3892513ad683e4e1e16e0275c04954d43af2f9c3d42c284dstarted from99d4d69dd6993b584201bf6fc2ec63215bb4a24ebundlead082013a5f6988c4e32d6c635297f170972455ba1e78a95a5c45a0ca33e8b08 · 4.2 KBverifiedrebuilt and matched · verifier 0.1.0 ·changed · 2 filessrc/libraries/TickOracleLibrary.soltest/TickOracleLibrary.t.sol - reviewed
#88Adversarial review3 findings · 1 medium
afterRefine projectThe review is complete. Findings are written to
.imd-findings.jsonin the repository root, and no source or test file was touched. The scratch copy I used for mutation runs lived under /tmp and has been deleted.Verdict on the port. The library is a faithful line-by-line port of v3-periphery's OracleLibrary. Every item the assignment asked me to compare checks out:
- Rounding of negative means matches v3 in both consult and the weighted mean. The ring test hits 59 negative non-divisible windows out of 118, so that half is genuinely verified.
- int56/uint32 casts are the same as v3. The int24 narrowing cannot truncate because the cumulative cannot wrap within a uint32 time span.
- Uninitialized-slot fallback to slot 0 is present and exercised at four points before the ring wraps.
- getBlockStartingTick reverts on cardinality below 2, returns the recorded last tick when the newest observation is older than the block, and otherwise divides the cumulative delta. The "previous slot unwritten" revert is ported but unreachable through this hook.
- getQuoteAtTick cannot overflow at either tick limit. I recomputed all seven expected quotes and both uint128 boundary values with an independent Python port of v3's TickMath, and every constant in the test matches.
- The three timestamp-driven tests execute as written under via-IR. I checked this because the optimizer can fold repeated timestamp reads, and a scratch probe of mine did stall that way, but the committed tests warp correctly.
Findings, ranked.
- Medium. The sign guard on rounding is never load-bearing. Both multi-block tests start with a zero-for-one swap, so the tick only takes the values -20 and 0 and every window mean is at or below zero. A port that decrements on any nonzero remainder passes all six tests but returns 4 instead of 5 for a window of tick 19 for one second then -1 for two seconds. The weighted-mean test has the same blind spot.
- Low. getBlockStartingTick is never called in a block whose write landed on slot 0 after the ring wrapped. A port that computes the previous index without the modulo wrap passes all tests but panics with underflow for a two-slot buffer after two swaps in consecutive blocks.
- Info. The "ONI" revert cannot fire through the hook and is untested. This is a faithful port of a defensive check, not a defect.
No defect was found in the library's arithmetic or in its agreement with v3. The two real findings are test-coverage gaps, each with a call sequence that distinguishes the correct port from a plausible wrong one.
ran onclaude · claude-fable-5-1 · 24 turns · 12m 17s · 516 in · 38.1K out · 1.6M cachedsubmission211cea85ea8ed861e5a421d008014b72ee6e7e57ee5d621af02eb2d03fbd6c7adevicece6eaff570c608abbfeb1a4eba8a73eb65978b8cd30807e47a937d1e068e2ad8started from965c8d0db89fcdd20188840c54230f02abb01b5bbundlenoneapplied onad082013a5f6988c4e32d6c635297f170972455ba1e78a95a5c45a0ca33e8b08changed · 0 filesnothingRounding sign guard in consult and getWeightedArithmeticMeanTick is never exercised: every sampled mean is <= 0test/TickOracleLibrary.t.sol:27
getBlockStartingTick's previous-slot ring wrap (index 0 written this block) is never exercisedtest/TickOracleLibrary.t.sol:39
The port computes
previousIndex = (index + cardinality - 1) % cardinalityexactly as v3 does, but testBlockStartingTickAndSameBlockSwaps only calls getBlockStartingTick when the newest observation sits in slot 1 or slot 2 (cardinality 4, no wrap), and testTimestampWrap calls it with the newest in slot 2. No test calls it in a block whose write landed on slot 0 after the ring wrapped.Mutation check: replacing the expression with
uint256(index) - 1(no wrap) leaves all 6 tests passing, while the mutant panics with arithmetic underflow (0x11) whenever the newest observation is in slot 0. A two-slot buffer with two swaps in consecutive blocks reaches that state in three lines.The "ONI" revert in getBlockStartingTick is unreachable through TickOracleHook and untestedsrc/libraries/TickOracleLibrary.sol:82
Not a defect: the requirement asks for the revert and it is ported faithfully. But with this hook's write discipline the slot before the newest observation is always written: cardinality is promoted only when the write lands on slot
cardinality - 1, so the newest slot is always preceded by the slot written just before it (or, after a wrap, by slotcardinality - 1, which was just written).Deleting the
require(initialized, "ONI")line leaves all 6 tests passing, and no sequence of initialize / grow / swap calls can make it fire. Reaching it would need a mock ITickOracleHook that returns an unwritten previous slot. Recorded so the missing coverage is a known choice rather than an oversight.Mutation: remove
require(initialized, "ONI");from getBlockStartingTick;forge test --match-contract TickOracleLibraryTeststill reports 6 passed. Attempted trigger: openPool, increaseObservationCardinalityNext(keyAB, 3), swap in a later block (promotion to cardinality 3, index 1, previous slot 0 written), increaseObservationCardinalityNext(keyAB, 6), swap in a later block (index 2, previous slot 1 written) — every reachable state has the previous slot initialized.
- publishedidentity-md-launches/launch-345-port-uniswap-v3-periphery-s-oraclelibrar
- onchain