Skip to content

MVP to the WindMill-Exchange Based on Modular structure using components and libs , storage, Math libraries#2

Merged
aniket866 merged 10 commits into
StabilityNexus:mainfrom
aniket866:MVP
Jun 8, 2026
Merged

MVP to the WindMill-Exchange Based on Modular structure using components and libs , storage, Math libraries#2
aniket866 merged 10 commits into
StabilityNexus:mainfrom
aniket866:MVP

Conversation

@aniket866

@aniket866 aniket866 commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Closes #1

PROject status after this PR

Area Status Notes
Core contract Complete WindmillExchange.sol — create, cancel, match
Errors Complete Declared inline in WindmillExchange.sol
Events Complete Declared inline in WindmillExchange.sol
Interfaces Complete IWindmillExchange, IERC20
Libraries Complete MathUtils, PriceCurve, TokenTransfer
Storage Complete OrderStorage, PairStorage
Types Complete Order struct, pairKeyOf helper
Deploy script Complete DeployWindmill.s.sol
Tests Complete WindmillExchange.t.sol — unit tests + 1 fuzz test

Function Visibility Description
createOrder(...) external Escrows tokens and registers a new order
cancelOrder(uint256) external Refunds escrowed tokens and removes order
matchOrders(uint256, uint256) external Validates crossing and settles a buy/sell pair
currentPrice(uint256, uint256) external view Returns order price at a given timestamp
getOrder(uint256) external view Returns full order struct
getOrdersByPair(address, address) external view Returns active order IDs for a pair

WindmillExchange — MVP

Adds a fully self-contained on-chain DEX with dynamic linear price curves and keeper-triggered matching.

Feature and its Use Cases

1 — Reward for matching orders

People who call matchOrders() should get a small fee for doing the work.

2 — Limit order list size per pair

Right now the order list can grow forever. Add a way to fetch orders in chunks so it stays cheap to read.

3 — Reset price after partial fill

When an order is only partly filled, its price should restart from that point — not keep counting from when it was first created.

4 — Better error for same token

If someone tries to trade a token for itself, show a SameToken error instead of the current ZeroAddress which is confusing.

5 — Expiry on match call

Let keepers set a deadline when calling matchOrders() so the trade can't be held and submitted late.

6 — Use a price feed for start price

Let orders pull the starting price from an on-chain oracle instead of always entering it manually.

7 — Admin controls

Add a basic owner role to pause the exchange and change settings when needed.

Files Added

File | Purpose
File Purpose
WindmillExchange.sol Core contract — createOrder, cancelOrder, matchOrders
OrderStorage.sol Order mapping + lifecycle management
PairStorage.sol Per-pair index with O(1) swap-and-pop removal
PriceCurve.sol Linear price-over-time with slope, clamp, and cross detection
MathUtils.sol 512-bit mulDiv, midpoint, clamp, abs
TokenTransfer.sol Safe ERC-20 transfer wrappers
OrderTypes.sol Order struct + pairKeyOf
IWindmillExchange.sol Public ABI interface
IERC20.sol Minimal ERC-20 interface
DeployWindmill_s.sol Foundry deploy script
WindmillExchange_t.sol Foundry test suite

Key Design Choices

  • CEI pattern throughout — state before transfers
  • Inline reentrancy guard — no OpenZeppelin required
  • Midpoint settlement — fair to both maker and taker
  • 15 custom errors — no string revert messages

Out of Scope

Fees, oracle integration, and governance are deferred to the next milestone.

Storage/Layout Impact:

  • No storage layout changes
  • Storage layout changed (explain below)

Gas and Performance:

Test Evidence:

  • forge fmt --check:
  • forge build --sizes:
  • forge test -vvv:
  • forge coverage:
  • forge snapshot:
image image

Deployment and Verification:

  • Requires deployment script updates
  • Requires env/config updates (PRIVATE_KEY, RPC URL, explorer key)
  • Requires explorer verification updates
  • No deployment impact

Screenshots/Recordings:

Additional Notes:

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions.
  • If applicable, I have made corresponding changes or additions to the documentation.
  • If applicable, I have made corresponding changes or additions to tests.
  • My changes generate no new warnings or errors.
  • I have joined the Stability Nexus's Discord server and I will share a link to this PR with the project maintainers there.
  • I have read the Contribution Guidelines.
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I reviewed contract access control and authorization paths.
  • I reviewed reentrancy/external-call ordering where relevant.
  • I confirmed storage compatibility (or documented intentional breakage).
  • I reviewed gas impact for hot paths.

AI Usage Disclosure

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: TODO

AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • New Features

    • Added a full on-chain order-book exchange with order creation, cancellation, matching, dynamic pricing, settlement events, and safe token transfers.
    • Added standard token interface and utility libraries for pricing, math, and safe transfers; storage primitives for order/pair management.
    • Added a deployment script for the new exchange.
  • Tests

    • Comprehensive tests and a mock token validating order lifecycle, pricing, matching, and balances.
  • Chores

    • Removed legacy Counter contract, its deployment script, and associated tests.

@Zahnentferner , @kaneki003 Please review

@coderabbitai

coderabbitai Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Removes example Counter contract, script, and tests; adds WindmillExchange MVP with order lifecycle, storage and pair indexing, price-curve, math and token transfer libraries, ERC20 and exchange interfaces, deployment script, and comprehensive Forge tests for matching and edge cases.

Changes

Cohort / File(s) Summary
Counter Removal
script/Counter.s.sol, src/Counter.sol, test/Counter.t.sol
Deleted example Counter contract, its Forge deployment script, and associated tests.
Deployment Script
script/DeployWindmill.s.sol
Added Forge script DeployWindmill that reads PRIVATE_KEY, broadcasts, deploys WindmillExchange, and logs the address.
Core Exchange
src/core/WindmillExchange.sol
New WindmillExchange contract: order creation, cancellation, matching, validations, settlement logic, events, reentrancy guard, pause/ownership hooks, and integrations with storage, price curve, and transfer helpers.
Types & Storage
src/types/OrderTypes.sol, src/storage/OrderStorage.sol, src/storage/PairStorage.sol
New Order struct and pairKeyOf; abstract storage for orders (auto-increment IDs, get/update/deactivate) and pair-indexing helpers with safe add/remove and retrieval.
Interfaces
src/interfaces/IERC20.sol, src/interfaces/IWindmillExchange.sol
Added standard IERC20 interface and IWindmillExchange interface declaring order API and view helpers.
Libraries
src/libraries/MathUtils.sol, src/libraries/PriceCurve.sol, src/libraries/TokenTransfer.sol
Math utilities (mulDiv, midpoint, clamp, abs), PriceCurve (price progression, crossing, settlement), and TokenTransfer safe ERC20 transfer wrappers with failure handling.
Tests
test/WindmillExchange.t.sol
New comprehensive Forge tests and MockERC20: fixtures and tests for create/cancel/match flows, price behavior, partial fills, reverts, events, balances, and pair-based queries.

Sequence Diagram

sequenceDiagram
    actor Maker as Maker
    participant WX as WindmillExchange
    participant OS as OrderStorage
    participant PS as PairStorage
    participant TT as TokenTransfer
    participant PC as PriceCurve
    participant Token as ERC20

    rect rgba(100,150,200,0.5)
        note over Maker,WX: Order Creation
        Maker->>WX: createOrder(tokenIn, tokenOut, amountIn, ...)
        WX->>WX: validate inputs & expiry
        WX->>OS: _storeOrder(order)
        OS-->>WX: orderId
        WX->>PS: _addOrderToPair(tokenIn, tokenOut, orderId)
        WX->>TT: safeTransferFrom(tokenIn, maker, WX, amountIn)
        TT->>Token: transferFrom(maker, WX, amountIn)
        Token-->>TT: success
        WX-->>Maker: returns orderId + emit OrderCreated
    end

    rect rgba(150,200,150,0.5)
        note over Maker,WX: Order Matching
        Maker->>WX: matchOrders(buyOrderId, sellOrderId, deadline)
        WX->>OS: _getOrder(buyOrderId), _getOrder(sellOrderId)
        OS-->>WX: buyOrder, sellOrder
        WX->>PC: currentPrice(buyOrder, ts)
        PC-->>WX: buyPrice
        WX->>PC: currentPrice(sellOrder, ts)
        PC-->>WX: sellPrice
        WX->>PC: hasCrossed(buy, sell, ts)
        PC-->>WX: crossed?
        WX->>WX: _computeSettlement(...)
        WX->>OS: _updateRemainingIn(...)
        WX->>TT: safeTransfer(tokenOut, buyer, amountOut)
        WX->>TT: safeTransfer(tokenIn, seller, amountIn)
        WX-->>Maker: emit OrderMatched / OrderFilled / OrderPartiallyFilled
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • StabilityNexus/Windmill-EVM-Contracts#12 — touches the same Counter example files that were removed here; potential overlap/conflict with example cleanup.

Suggested labels

Solidity Lang

Poem

🐰 I hopped through code to plant a trade,
Curves that bend where prices wade,
Orders sprout and tokens flow,
Keepers grin as windmills go,
Hooray — a matched trade! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive PR description is comprehensive but incomplete against template: missing addressed issues section, gas/performance details, test evidence snippets, and deployment verification status. Complete the 'Addressed Issues' section with explicit issue number, add detailed gas impact analysis, include forge test output snippets, and clarify deployment requirements and verification steps.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed All seven feature requirements from issue #1 are implemented: keeper rewards (in matchOrders), paginated order retrieval (getOrdersByPair), price reset after partial fills, SameToken error, deadline parameter on matchOrders, price feed support, and admin/pause controls.
Out of Scope Changes check ✅ Passed All code changes directly support the MVP implementation. Removals (Counter.sol, Counter.t.sol, Counter.s.sol) appear to be placeholder cleanup; all additions align with the WindmillExchange MVP scope.
Title check ✅ Passed The title mentions WindMill-Exchange and modular structure with components, libs, and storage, which aligns with the PR's focus on introducing an MVP DEX implementation with libraries and storage contracts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/WindmillExchange.sol`:
- Around line 239-243: Compute paymentOwed from the asset amount only after
ensuring the asset can be fully paid at the rounded price: keep the existing
maxAssetFromBuy and initial filledAsset calculation (using MathUtils.mulDiv,
sell.remainingIn, buy.remainingIn, settlementPx), then compute paymentOwed =
MathUtils.mulDiv(filledAsset, MathUtils.RAY, settlementPx) and immediately
recompute filledAsset = MathUtils.mulDiv(paymentOwed, settlementPx,
MathUtils.RAY) so filledAsset is the asset amount implied by the floored
payment; if this reduces filledAsset to zero or otherwise makes the fill invalid
handle it (revert or skip) instead of sending more tokenIn than paymentOwed
supports. Ensure you reference filledAsset, paymentOwed, MathUtils.mulDiv,
settlementPx, buy.remainingIn and sell.remainingIn when making the change.
- Around line 64-109: The createOrder function registers the order (via
_storeOrder and _addOrderToPair) before performing the external
TokenTransfer.safeTransferFrom, allowing a reentrant token callback to call
matchOrders against an order without funds; fix by preventing reentrancy: either
add the nonReentrant modifier to createOrder (preferred) or move
TokenTransfer.safeTransferFrom before calling _storeOrder/_addOrderToPair so
external calls cannot reenter into matchOrders using the un-funded order;
reference functions: createOrder, _storeOrder, _addOrderToPair,
TokenTransfer.safeTransferFrom, and matchOrders.

In `@src/libraries/MathUtils.sol`:
- Around line 76-79: The clamp function can return a value even when lo > hi;
add an explicit validation at the start of MathUtils.clamp to revert when lo >
hi (e.g., require or revert with a clear error) so callers like
PriceCurve._compute never receive a nonsensical value, and keep the existing
behavior when lo <= hi so the later checks in clamp (value < lo / value > hi)
remain unchanged.

In `@src/libraries/PriceCurve.sol`:
- Around line 49-50: The negation of slope can overflow when slope ==
type(int256).min; update MathUtils.abs(int256) to explicitly check for slope ==
type(int256).min and revert with SlopeOverflow() (or return a safe handling)
instead of performing uint256(-x), and mirror that same check in
PriceCurve._compute() (replace uint256(-slope) usage with a branch that checks
for type(int256).min and reverts SlopeOverflow() or handles it safely before
converting to uint256). Ensure both the validation path (where MathUtils.abs is
used) and price computation path explicitly handle this edge case, and add
regression tests that call MathUtils.abs(type(int256).min) and
PriceCurve._compute() (or the public compute wrapper) with slope ==
type(int256).min to assert the SlopeOverflow() behavior.

In `@src/libraries/TokenTransfer.sol`:
- Around line 9-16: The helper currently calls token.call which returns
(true,"") for EOAs/no-code addresses, so add a pre-call code-existence check: in
safeTransfer (and the analogous safeTransferFrom) verify token.code.length > 0
(or address(token).code.length > 0) and revert (e.g., TransferFailed() or a new
descriptive error) if code length is zero before performing the low-level call;
then keep the existing ok/data decoding checks. Also add a regression test that
passes a no-code address as token to both safeTransfer and safeTransferFrom to
assert they revert rather than succeed silently.

In `@src/storage/PairStorage.sol`:
- Around line 7-8: The _pairIndex mapping is currently global and must be
namespaced by pair to avoid cross-pair collisions; change the declaration from
mapping(uint256 => uint256) _pairIndex to mapping(bytes32 => mapping(uint256 =>
uint256)) _pairIndex (keyed by the same pair key used for _pairOrders) and
update every place that reads/writes _pairIndex (e.g., in the functions that
push, remove, swap and update order indexes) to pass the pair key first (e.g.,
_pairIndex[pair][orderId]) so insertions, deletions and index updates operate on
the correct pair bucket and the pop/update logic uses the per-pair index rather
than a global one.

In `@test/WindmillExchange.t.sol`:
- Around line 265-345: Update the three tests
(test_matchOrders_success_fullFill, test_matchOrders_partialFill_buySmaller,
test_matchOrders_partialFill_sellSmaller) to assert exact post-trade balances
instead of only asserting positive deltas: compute expected token transfers
using the RAY-scaled price and the filled amounts (use the same math as
WindmillExchange.matchOrders: paymentOwed = filledIn * price / RAY and
filledAsset = filledOut) and replace assertGt checks with assertEq against those
computed expected balances; additionally add a 1-wei seller-limited match case
(create a scenario where seller remainingIn is limited by 1 wei) and assert
exact balances there to catch rounding edge-cases. Ensure you reference the
order IDs (buyId, sellId) and read Order.remainingIn from exchange.getOrder to
compute the actual filled amounts when asserting expected balances.
- Around line 179-220: Add a new unit test named
test_createOrder_revert_invalidPriceBounds that vm.prank(alice),
vm.expectRevert(InvalidPriceBounds.selector) and then calls exchange.createOrder
with tokenA/tokenB and parameters that violate the price-bounds check (e.g.,
provide startPrice and endPrice in the wrong order or out-of-range so the
contract's InvalidPriceBounds branch is hit); mirror the style of the existing
tests (use alice, tokenA/tokenB, amount like 100 ether, RAY constant, and other
zeros) so the new test validates the createOrder InvalidPriceBounds revert path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25722536-e6ac-469c-a567-e52eea99c840

📥 Commits

Reviewing files that changed from the base of the PR and between 15f4ba1 and f4ede2f.

📒 Files selected for processing (14)
  • script/Counter.s.sol
  • script/DeployWindmill.s.sol
  • src/Counter.sol
  • src/core/WindmillExchange.sol
  • src/interfaces/IERC20.sol
  • src/interfaces/IWindmillExchange.sol
  • src/libraries/MathUtils.sol
  • src/libraries/PriceCurve.sol
  • src/libraries/TokenTransfer.sol
  • src/storage/OrderStorage.sol
  • src/storage/PairStorage.sol
  • src/types/OrderTypes.sol
  • test/Counter.t.sol
  • test/WindmillExchange.t.sol
💤 Files with no reviewable changes (3)
  • test/Counter.t.sol
  • script/Counter.s.sol
  • src/Counter.sol

Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/libraries/MathUtils.sol
Comment thread src/libraries/PriceCurve.sol Outdated
Comment thread src/libraries/TokenTransfer.sol
Comment thread src/storage/PairStorage.sol Outdated
Comment thread test/WindmillExchange.t.sol
Comment thread test/WindmillExchange.t.sol Outdated
aniket866 and others added 3 commits March 16, 2026 12:53
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/WindmillExchange.t.sol`:
- Around line 191-195: The test asserts ZeroAddress.selector for same-token
orders but the PR requires replacing that with a new SameToken error; add a
custom error SameToken() to WindmillExchange and update the createOrder logic
(the branch that currently reverts when tokenA == tokenB) to revert with
SameToken instead of ZeroAddress, and then update the test
test_createOrder_revert_sameToken to expect SameToken.selector; locate checks in
createOrder and the test function by the identifiers createOrder and
test_createOrder_revert_sameToken.
- Around line 437-483: Add a test that verifies currentPrice increases over time
with a positive slope: create a buy order via _createBuyOrder (like in
test_currentPrice_descendingSlope_decreasesOverTime) with a positive int256
slope (e.g., slope = int256(RAY / 1000)), capture t0 = block.timestamp, call
exchange.currentPrice(id, t0) and exchange.currentPrice(id, t0 + delta), assert
the initial price equals startPrice and that priceLater > priceNow (using
assertGt) to confirm ascending behavior; keep the test name consistent (e.g.,
test_currentPrice_ascendingSlope_increasesOverTime) and mirror the structure of
the descending slope test.
- Around line 358-431: Add a test that asserts matchOrders reverts with
OrderInactive.selector when either order is inactive: create a buy and sell
order via _createBuyOrder/_createSellOrder, then in one variant cancel the buy
via exchange.cancelOrder (or fully fill it via matchOrders with another
counterparty) and in another variant cancel the sell, and call
exchange.matchOrders(buyId, sellId) expecting
vm.expectRevert(OrderInactive.selector); ensure you reference matchOrders,
OrderInactive.selector, _createBuyOrder/_createSellOrder and cancelOrder (or the
helper used to fully fill) so the test targets the inactive-order path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: de4fce50-9247-4610-a870-2492980f6cc7

📥 Commits

Reviewing files that changed from the base of the PR and between f4ede2f and 93ee745.

📒 Files selected for processing (1)
  • test/WindmillExchange.t.sol

Comment thread test/WindmillExchange.t.sol
Comment thread test/WindmillExchange.t.sol
Comment thread test/WindmillExchange.t.sol

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (3)
test/WindmillExchange.t.sol (2)

207-211: ⚠️ Potential issue | 🟠 Major

Same-token revert expectation is still targeting the wrong error.

Line 209 expects ZeroAddress.selector; this should validate the explicit same-token error path (SameToken) defined by the MVP objective.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/WindmillExchange.t.sol` around lines 207 - 211, The test
test_createOrder_revert_sameToken is asserting the wrong revert: replace the
current vm.expectRevert(ZeroAddress.selector) with
vm.expectRevert(SameToken.selector) so the call to
exchange.createOrder(address(tokenA), address(tokenA), ...) asserts the explicit
same-token failure path; ensure the SameToken selector symbol is
imported/visible to the test so the expectation references the contract error
type used by createOrder.

307-310: ⚠️ Potential issue | 🟠 Major

Settlement tests are still too weak to catch rounding/payment bugs.

These checks only assert positive deltas, not exact settlement amounts. Please assert exact post-trade balances (including a 1-wei edge case) so price/rounding regressions fail deterministically.
As per coding guidelines, "Ensure security-sensitive logic changes are not introduced without adequate test coverage."

Also applies to: 343-346

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/WindmillExchange.t.sol` around lines 307 - 310, Replace the loose
assertGt checks on token balances (tokenB.balanceOf(alice),
tokenA.balanceOf(bob) vs aliceTokenBBefore, bobTokenABefore) with exact equality
assertions that compute and compare against the expected post-trade balances
(e.g., aliceExpected = aliceTokenBBefore + expectedFilledAmount, bobExpected =
bobTokenABefore + expectedPaymentOwed), using the same calculation logic used by
the matcher/settlement code (price, amounts, rounding mode) so rounding
regressions fail deterministically; also add an additional test case exercising
a 1-wei remainder (edge case) and assert the exact expected balances for that
case too, referencing the same variables and functions (tokenA.balanceOf,
tokenB.balanceOf, expectedFilled/paymentOwed calculation) so the assertions fail
if rounding/payment changes.
src/storage/PairStorage.sol (1)

7-8: ⚠️ Potential issue | 🔴 Critical

Compilation blocker: duplicate _pairIndex declaration and missing _pairOrders state variable.

Line 8 redeclares _pairIndex with an incompatible type, and _pairOrders is referenced at lines 13, 14, 22, and 40 but is never declared. The file cannot compile in its current state.

Recommended fix
 abstract contract PairStorage {
     mapping(bytes32 => mapping(uint256 => uint256)) private _pairIndex;
-    mapping(uint256 => uint256) private _pairIndex;
+    mapping(bytes32 => uint256[]) private _pairOrders;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/storage/PairStorage.sol` around lines 7 - 8, Remove the duplicate mapping
declaration "mapping(uint256 => uint256) private _pairIndex" and instead declare
the missing _pairOrders state variable with the correct type and visibility used
throughout the contract (e.g., "mapping(bytes32 => uint256[]) private
_pairOrders" if the code treats _pairOrders as an array of order IDs keyed by
pairHash); keep the original mapping(bytes32 => mapping(uint256 => uint256))
private _pairIndex as-is, and ensure all references to _pairOrders match that
newly added mapping type and visibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/WindmillExchange.sol`:
- Around line 63-73: The createOrder function currently accepts and stores
caller-supplied startPrice directly; add an on-chain oracle resolution path
instead: add an optional oracle identifier parameter (e.g., oracleId or
priceFeed) to createOrder, branch on its presence to fetch the current price via
the contract's oracle interface (e.g., call getPrice/peek on the
PriceFeed/Oracle contract), validate the fetched price (non-zero, within
bounds), and persist the resolved startPrice and the oracle identifier in the
order record (instead of trusting the caller value). Also keep a clear
fallback/require path if no oracle is provided (or make oracle mandatory), and
ensure any oracle calls are gas-safe and use the same symbols present in this
contract (createOrder, startPrice, orders mapping/Order struct, and oracle
interface) so storage and event emission remain consistent.
- Around line 26-35: Add an admin/pauser governance surface to WindmillExchange:
introduce an owner/admin state (or inherit OpenZeppelin Ownable) and a paused
boolean plus modifiers like onlyOwner (or onlyAdmin) and whenNotPaused to guard
state-changing functions (e.g., deposit/withdraw/trade functions and any
functions in PairStorage/OrderStorage that modify state), implement pause() and
unpause() setter functions callable only by the owner/admin, and ensure
nonReentrant remains intact by keeping the existing _reentrancyStatus and
nonReentrant modifier; update function signatures to include whenNotPaused where
appropriate so the exchange can be frozen and admin settings adjusted securely.
- Around line 12-17: Declare and use a dedicated SameToken() error instead of
reusing ZeroAddress() for same-asset orders: add error SameToken(); to the
top-level errors list and update the order validation logic (the place that
currently checks makerToken == takerToken and reverts with ZeroAddress()) to
instead revert with SameToken() when makerToken == takerToken; ensure any tests
or callers expecting ZeroAddress() for this condition are updated to expect
SameToken().
- Around line 188-195: The current getOrdersByPair(tokenA, tokenB) materializes
the entire order id array and must be converted to a paginated API to avoid
unbounded gas/copy; change the external signature to accept pagination params
(e.g. getOrdersByPair(address tokenA, address tokenB, uint256 cursor, uint256
limit) external view returns (uint256[] memory)), update the override and any
interface accordingly, and implement a paginated iterator in _getOrdersByPair
(or create _getOrdersByPairPaginated) that precomputes a bounded size =
min(limit, remaining) and loops from cursor to cursor+size (no unbounded loops),
copies only that window into the result array, and enforce a sane max limit
guard to prevent abuse. Ensure callers are updated to use the new paginated
form.

In `@src/interfaces/IWindmillExchange.sol`:
- Line 21: The IWindmillExchange.matchOrders API lacks the promised deadline
parameter; update the interface function signature for matchOrders in
IWindmillExchange to accept a uint256 deadline (e.g., change matchOrders(uint256
buyOrderId, uint256 sellOrderId) to include a deadline parameter), keep external
visibility, and then propagate this signature change to all implementations and
callers (contracts and tests) so they pass and enforce the per-call deadline
when executing matchOrders.

---

Duplicate comments:
In `@src/storage/PairStorage.sol`:
- Around line 7-8: Remove the duplicate mapping declaration "mapping(uint256 =>
uint256) private _pairIndex" and instead declare the missing _pairOrders state
variable with the correct type and visibility used throughout the contract
(e.g., "mapping(bytes32 => uint256[]) private _pairOrders" if the code treats
_pairOrders as an array of order IDs keyed by pairHash); keep the original
mapping(bytes32 => mapping(uint256 => uint256)) private _pairIndex as-is, and
ensure all references to _pairOrders match that newly added mapping type and
visibility.

In `@test/WindmillExchange.t.sol`:
- Around line 207-211: The test test_createOrder_revert_sameToken is asserting
the wrong revert: replace the current vm.expectRevert(ZeroAddress.selector) with
vm.expectRevert(SameToken.selector) so the call to
exchange.createOrder(address(tokenA), address(tokenA), ...) asserts the explicit
same-token failure path; ensure the SameToken selector symbol is
imported/visible to the test so the expectation references the contract error
type used by createOrder.
- Around line 307-310: Replace the loose assertGt checks on token balances
(tokenB.balanceOf(alice), tokenA.balanceOf(bob) vs aliceTokenBBefore,
bobTokenABefore) with exact equality assertions that compute and compare against
the expected post-trade balances (e.g., aliceExpected = aliceTokenBBefore +
expectedFilledAmount, bobExpected = bobTokenABefore + expectedPaymentOwed),
using the same calculation logic used by the matcher/settlement code (price,
amounts, rounding mode) so rounding regressions fail deterministically; also add
an additional test case exercising a 1-wei remainder (edge case) and assert the
exact expected balances for that case too, referencing the same variables and
functions (tokenA.balanceOf, tokenB.balanceOf, expectedFilled/paymentOwed
calculation) so the assertions fail if rounding/payment changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 93b363b9-960b-4561-8351-d4dc8915f84d

📥 Commits

Reviewing files that changed from the base of the PR and between 93ee745 and 2834320.

📒 Files selected for processing (10)
  • script/DeployWindmill.s.sol
  • src/core/WindmillExchange.sol
  • src/interfaces/IWindmillExchange.sol
  • src/libraries/MathUtils.sol
  • src/libraries/PriceCurve.sol
  • src/libraries/TokenTransfer.sol
  • src/storage/OrderStorage.sol
  • src/storage/PairStorage.sol
  • src/types/OrderTypes.sol
  • test/WindmillExchange.t.sol

Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/interfaces/IWindmillExchange.sol Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
test/WindmillExchange.t.sol (3)

497-509: 🧹 Nitpick | 🔵 Trivial

Consider adding ascending slope test for completeness.

The currentPrice tests validate flat and descending slope behavior. An ascending slope test would complete the price curve coverage.

🧪 Optional: ascending slope test
+    function test_currentPrice_ascendingSlope_increasesOverTime() public {
+        uint256 startPrice = RAY;
+        int256 slope = int256(RAY / 1000);  // price increases
+
+        uint256 id = _createBuyOrder(alice, 100 ether, startPrice, slope, 0);
+        uint256 t0 = block.timestamp;
+
+        uint256 priceNow = exchange.currentPrice(id, t0);
+        uint256 priceLater = exchange.currentPrice(id, t0 + 1000);
+
+        assertEq(priceNow, startPrice);
+        assertGt(priceLater, priceNow, "price should increase over time with positive slope");
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/WindmillExchange.t.sol` around lines 497 - 509, Add a complementary test
that verifies a positive (ascending) slope increases price over time: create a
new test function (e.g., test_currentPrice_ascendingSlope_increasesOverTime)
that mirrors test_currentPrice_descendingSlope_decreasesOverTime but sets slope
to a positive value (e.g., int256(RAY / 1000)), calls _createBuyOrder(alice, ...
, startPrice, slope, 0), uses exchange.currentPrice(id, t0) and
exchange.currentPrice(id, t0 + delta) and assert that priceLater > priceNow (and
assertEq for initial price if desired) to cover the ascending slope case.

410-486: 🧹 Nitpick | 🔵 Trivial

Consider adding test_matchOrders_revert_orderInactive test.

The match revert tests cover expired, self-match, no-cross, and pair-mismatch scenarios, but there's no test verifying that matchOrders reverts with OrderInactive when attempting to match a cancelled order. This was noted in a previous review as addressed, but the test is not present in the current file.

🧪 Suggested test case
+    function test_matchOrders_revert_orderInactive() public {
+        uint256 buyId = _createBuyOrder(alice, 100 ether, RAY, 0, 0);
+        uint256 sellId = _createSellOrder(bob, 100 ether, RAY, 0, 0);
+
+        // Cancel the buy order to make it inactive
+        vm.prank(alice);
+        exchange.cancelOrder(buyId);
+
+        vm.expectRevert(OrderInactive.selector);
+        exchange.matchOrders(buyId, sellId, block.timestamp + 1);
+    }

As per coding guidelines, "Ensure failure paths and revert scenarios are explicitly handled and validated."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/WindmillExchange.t.sol` around lines 410 - 486, Add a new unit test
named test_matchOrders_revert_orderInactive that creates a buy and a sell order
using the existing helpers (_createBuyOrder/_createSellOrder), cancels one of
them via exchange.cancelOrder (using vm.prank as the order owner), then calls
exchange.matchOrders(...) and asserts it reverts with OrderInactive.selector;
reference _createBuyOrder, _createSellOrder, exchange.cancelOrder,
OrderInactive, and matchOrders so the test mirrors the other revert tests (use
vm.expectRevert(OrderInactive.selector) and pass block.timestamp + 1 to
matchOrders).

326-353: ⚠️ Potential issue | 🟡 Minor

Settlement assertions should verify exact amounts, not just positive deltas.

Lines 344-346 use assertGt to verify balance increases, but this doesn't catch settlement math bugs. A rounding error in the exchange could still pass these assertions while paying incorrect amounts.

💡 Suggested exact assertion approach
     function test_matchOrders_success_fullFill() public {
         uint256 price = RAY; // 1 tokenA per tokenB
         uint256 amount = 100 ether;

         uint256 buyId = _createBuyOrder(alice, amount, price, 0, 0);
         uint256 sellId = _createSellOrder(bob, amount, price, 0, 0);

         uint256 aliceTokenBBefore = tokenB.balanceOf(alice);
         uint256 bobTokenABefore = tokenA.balanceOf(bob);

         exchange.matchOrders(buyId, sellId, block.timestamp + 1);

         // Both orders should be deactivated (fully filled)
         assertFalse(exchange.getOrder(buyId).active, "buy should be inactive");
         assertFalse(exchange.getOrder(sellId).active, "sell should be inactive");

-        // Alice should have received tokenB (filledAsset from sell order)
-        assertGt(tokenB.balanceOf(alice), aliceTokenBBefore, "alice should receive tokenB");
-        // Bob should have received tokenA (paymentOwed from buy order)
-        assertGt(tokenA.balanceOf(bob), bobTokenABefore, "bob should receive tokenA");
+        // At price = 1 RAY, filledAsset = amount, paymentOwed = amount
+        // Keeper fee = paymentOwed / 1000 = amount / 1000
+        uint256 keeperFee = amount / 1000;
+        
+        // Alice receives full filledAsset (sell.tokenIn = tokenB)
+        assertEq(tokenB.balanceOf(alice), aliceTokenBBefore + amount, "alice should receive exact tokenB");
+        // Bob receives paymentOwed minus keeper fee
+        assertEq(tokenA.balanceOf(bob), bobTokenABefore + amount - keeperFee, "bob should receive exact tokenA");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/WindmillExchange.t.sol` around lines 326 - 353, The
test_matchOrders_success_fullFill uses assertGt to only check positive balance
deltas, which can mask settlement math/rounding bugs; replace those loose checks
by computing the exact expected token transfers from the created orders (use the
price, amount, and the created buyId/sellId via _createBuyOrder/_createSellOrder
and the exchange.matchOrders call) and assert equality: verify
tokenB.balanceOf(alice) - aliceTokenBBefore == expectedFilledAssetFromSellOrder
and tokenA.balanceOf(bob) - bobTokenABefore == expectedPaymentOwedFromBuyOrder
(use exchange.getOrder or your internal calc to derive expected amounts) and
keep the other assertions (orders inactive, getOrdersByPair empty) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/WindmillExchange.sol`:
- Around line 4-6: Extract the inline interface IPriceOracle into its own
Solidity interface file (create IPriceOracle.sol containing interface
IPriceOracle { function getPrice() external view returns (uint256); }) and
remove the inline declaration from WindmillExchange.sol; then add an import of
the new interface into WindmillExchange.sol and ensure any references to
IPriceOracle (e.g., in functions or state variables) compile against the
imported interface name.
- Around line 30-53: The contract sets owner in the constructor but provides no
way to change it; add an ownership transfer mechanism by implementing a
transferOwnership(address newOwner) function guarded by onlyOwner that requires
newOwner != address(0), updates the owner state variable, and emits an
OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
event; also consider adding renounceOwnership() (onlyOwner => owner = address(0)
+ emit event) and update any access checks that rely on owner accordingly.
- Around line 224-242: getOrdersByPair currently pulls the entire array into
memory via _getOrdersByPair and will underflow if cursor > all.length; change to
read the orders directly from storage by adding an internal view that returns
uint256[] storage (e.g. _getOrdersByPairStorage using
_pairOrders[pairKeyOf(tokenA, tokenB)]), perform a bounds check that reverts
with a clear message if cursor >= storageArray.length, compute remaining/size
from storageArray.length, then allocate the result array and copy only the
requested window (storageArray[cursor + i]) into memory to avoid loading the
full list.
- Around line 101-103: The call to IPriceOracle(priceFeed).getPrice() in
WindmillExchange (setting startPrice when priceFeed != address(0)) needs
defensive handling: wrap the external call in a try/catch around
IPriceOracle(priceFeed).getPrice() to avoid reverts bubbling up, validate the
returned price (startPrice) against reasonable bounds (e.g., non-zero, within
configured min/max or a change threshold) and, if the oracle exposes timestamps,
perform a staleness check against block.timestamp; on failure or invalid values,
fall back to a safe behavior (revert with clear error, use a cached price, or
skip oracle-dependent logic) and emit/log an event so callers can detect oracle
issues. Ensure references to priceFeed, startPrice, and IPriceOracle.getPrice
are the focal points of the patch.

---

Duplicate comments:
In `@test/WindmillExchange.t.sol`:
- Around line 497-509: Add a complementary test that verifies a positive
(ascending) slope increases price over time: create a new test function (e.g.,
test_currentPrice_ascendingSlope_increasesOverTime) that mirrors
test_currentPrice_descendingSlope_decreasesOverTime but sets slope to a positive
value (e.g., int256(RAY / 1000)), calls _createBuyOrder(alice, ... , startPrice,
slope, 0), uses exchange.currentPrice(id, t0) and exchange.currentPrice(id, t0 +
delta) and assert that priceLater > priceNow (and assertEq for initial price if
desired) to cover the ascending slope case.
- Around line 410-486: Add a new unit test named
test_matchOrders_revert_orderInactive that creates a buy and a sell order using
the existing helpers (_createBuyOrder/_createSellOrder), cancels one of them via
exchange.cancelOrder (using vm.prank as the order owner), then calls
exchange.matchOrders(...) and asserts it reverts with OrderInactive.selector;
reference _createBuyOrder, _createSellOrder, exchange.cancelOrder,
OrderInactive, and matchOrders so the test mirrors the other revert tests (use
vm.expectRevert(OrderInactive.selector) and pass block.timestamp + 1 to
matchOrders).
- Around line 326-353: The test_matchOrders_success_fullFill uses assertGt to
only check positive balance deltas, which can mask settlement math/rounding
bugs; replace those loose checks by computing the exact expected token transfers
from the created orders (use the price, amount, and the created buyId/sellId via
_createBuyOrder/_createSellOrder and the exchange.matchOrders call) and assert
equality: verify tokenB.balanceOf(alice) - aliceTokenBBefore ==
expectedFilledAssetFromSellOrder and tokenA.balanceOf(bob) - bobTokenABefore ==
expectedPaymentOwedFromBuyOrder (use exchange.getOrder or your internal calc to
derive expected amounts) and keep the other assertions (orders inactive,
getOrdersByPair empty) intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca2f2696-2ee8-41b7-b6c7-01b9f762bf83

📥 Commits

Reviewing files that changed from the base of the PR and between 2834320 and 68ae016.

📒 Files selected for processing (4)
  • src/core/WindmillExchange.sol
  • src/interfaces/IWindmillExchange.sol
  • src/storage/PairStorage.sol
  • test/WindmillExchange.t.sol

Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/WindmillExchange.sol`:
- Around line 55-61: The pause/unpause functions change protocol state but don't
emit events; declare and use events (e.g., event Paused(address account); event
Unpaused(address account);) and emit them inside pause() and unpause()
respectively (emit Paused(msg.sender); and emit Unpaused(msg.sender);) so
callers monitoring the WindmillExchange contract (functions pause and unpause)
can observe state transitions and the actor who triggered them.
- Around line 224-243: The getOrdersByPair function currently accepts an
unbounded limit which can allow very large loops; introduce a hard cap constant
(e.g., uint256 constant MAX_PAGE_SIZE) and enforce size = min(remaining,
min(limit, MAX_PAGE_SIZE)) before allocating result to guarantee bounded gas
usage; update getOrdersByPair to apply this cap and validate limit (treat zero
as zero-page or revert per project convention), and mention/_use the existing
_getOrdersByPairStorage(tokenA, tokenB) storage lookup in your change so callers
still receive the same paginated items but never more than MAX_PAGE_SIZE per
call.
- Around line 110-133: The order is stored using the requested amountIn before
the external transfer (TokenTransfer.safeTransferFrom), which allows
fee-on-transfer/non-standard ERC20s to cause the contract to receive less than
order.remainingIn; update the logic in the order creation flow to verify and
enforce escrow invariants immediately after the transfer: perform the
safeTransferFrom, read the token balance delta (e.g.,
beforeBalance/afterBalance) and then either (a) revert if the received amount <
amountIn, or (b) set order.remainingIn (and amountIn) to the actual received
amount and persist via _storeOrder/_addOrderToPair so cancelOrder and
matchOrders use the true escrowed amount; ensure the change references
TokenTransfer.safeTransferFrom, _storeOrder, _addOrderToPair, order.remainingIn,
cancelOrder and matchOrders so all later flows rely on the verified received
amount.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a0525d7a-4139-4ec3-8b4d-4b85f9db11f2

📥 Commits

Reviewing files that changed from the base of the PR and between 68ae016 and 70fb04b.

📒 Files selected for processing (1)
  • src/core/WindmillExchange.sol

Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol
@aniket866 aniket866 changed the title MVP to the WindMill-Exchange MVP to the WindMill-Exchange Based on Modular structure keeping components. Mar 16, 2026
@aniket866 aniket866 changed the title MVP to the WindMill-Exchange Based on Modular structure keeping components. MVP to the WindMill-Exchange Based on Modular structure using components and libs/storage/Math libraries Mar 16, 2026
@aniket866 aniket866 changed the title MVP to the WindMill-Exchange Based on Modular structure using components and libs/storage/Math libraries MVP to the WindMill-Exchange Based on Modular structure using components and libs , storage, Math libraries Mar 16, 2026
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
Comment thread src/core/WindmillExchange.sol Outdated
@aniket866

Copy link
Copy Markdown
Contributor Author

hi @kaneki003 thankyou for review , I will correct all of them

acutally this pr was created a way before I started making proposal, at that time I had no indepth knowledge about this project

later I studied the project in detail during making proposal

so I will now correct this PR you can point out the wrong/irrelevent codes

Comment thread src/core/WindmillExchange.sol
Comment thread src/core/WindmillExchange.sol
Comment thread src/libraries/MathUtils.sol
Comment thread src/libraries/MathUtils.sol
Comment thread src/libraries/PriceCurve.sol
Comment thread src/libraries/PriceCurve.sol Outdated
Comment thread src/libraries/PriceCurve.sol
Comment thread src/storage/OrderStorage.sol
Comment thread src/storage/PairStorage.sol
@aniket866 aniket866 requested a review from kaneki003 May 31, 2026 14:07

@kaneki003 kaneki003 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aniket866 aniket866 merged commit e97dbc9 into StabilityNexus:main Jun 8, 2026
3 checks passed
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.

[FEATURE]: MVP to the WindMill-Exchange

2 participants