Skip to content

Raindex Router Arb contract#2421

Open
rouzwelt wants to merge 13 commits intomainfrom
2026-01-28-raindex-router-arb
Open

Raindex Router Arb contract#2421
rouzwelt wants to merge 13 commits intomainfrom
2026-01-28-raindex-router-arb

Conversation

@rouzwelt
Copy link
Contributor

@rouzwelt rouzwelt commented Jan 28, 2026

Motivation

This PR adds Raindex Router Arb contract, which can perform routed takeOrders() by doing a flash loan and making a intermediate swap through external source (or even can be another raindex order although this PR only implements the sushi RP handler), here is a simple example:

  • Assume we have orders A/B and wanna trade it, but there is no exact mirror order (B/A) and external liquidity sources (such as Sushi or Balancer) also don't have such pool and offer
  • We have another order with C/A
  • There is some route in external source for B -> C
  • We can trade these 2 order against each other, first taking a flash loan of token A to take the first order, we will get B as a result, next swap B through external sources for market price and get C, then take the last order and complete the circuit
  • Pay back the flash loan
  • Take whatever profit left from A and C tokens (there cant be any token B profits since it all is swapped to C through external source)

Note: The maximum clearable amount in this case cannot exceed total orderbook balance of token A / 2, thats because we cant take all the orderbook token A balance as loan since we need them for the last takeOrder() to complete, example:
Assume the order C/A has deposited 100 A tokens, and thats all the A tokens orderbook has, so at most half of it can be flash loaned, which goes towards taking the first order and next half that stayed in orderbook for taking the last order

Solution

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • New Features

    • Flash-loan driven arbitrage router for cross-orderbook arbitrage flows
    • On-chain multi-leg swap routing with Sushi leg support for dynamic asset conversion
    • ARB-network deployment support for the new router
  • Bug Fixes

    • Guarded order processing to avoid division-by-zero in IO ratio calculations
  • Tests

    • End-to-end tests covering arbitrage execution, vault accounting, and balance assertions

@rouzwelt rouzwelt self-assigned this Jan 28, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

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

Adds an abstract flash-loan arbitrage router and a concrete Raindex router with on-chain route-leg processing (Sushi support), wires the new router into the ARB deployment, adds end-to-end tests/mocks for arb4, and adds a zero-division guard in OrderBookV6.takeOrders4.

Changes

Cohort / File(s) Summary
Abstract Arb Router
src/abstract/OrderBookV6RaindexRouter.sol
New abstract contract implementing IERC3156 flash-borrower flow: onFlashLoan handling, arb4 orchestration, supportsInterface, custom errors, and abstract _exchange hook for asset conversion.
Concrete Router (Raindex)
src/concrete/arb/RaindexRouterOrderBookV6Arb.sol
New concrete router extending the abstract router. Adds RouteLegType/RouteLeg, decodes RouteLeg[] in _exchange, implements Sushi leg via _processSushiLeg with token approvals and route processing; fallback included.
OrderBook IO Guard
src/concrete/ob/OrderBookV6.sol
Fix in takeOrders4 (else branch): guard against zero IORatio to avoid divide-by-zero when computing takerInput.
Deployment Script
script/Deploy.sol
Imports and instantiates RaindexRouterOrderBookV6Arb in the ARB deployment path using OrderBookV6ArbConfig with zeroed interpreters/signed context.
Tests & Mocks
test/concrete/arb/RaindexRouterOrderBookV6Arb.t.sol
New Foundry test exercising arb4 end-to-end: Token mock, MockSushiRP, vault setup, two-order arbitrage scenario, assertions on balances and arber bounty; helper constructors for orders/configs.

Sequence Diagrams

sequenceDiagram
    participant User
    participant DeployScript as Deploy Script
    participant RaindexRouter as RaindexRouterOrderBookV6Arb
    participant OrderBook as OrderBookV6
    participant FlashLender as Flash Lender
    participant RouteProcessor as RouteProcessor (Sushi)

    User->>DeployScript: run deploy
    DeployScript->>RaindexRouter: instantiate with config

    User->>RaindexRouter: call arb4(orderBook, takeOrders, exchangeData)
    RaindexRouter->>OrderBook: query vault balance / compute flashLoanAmount
    RaindexRouter->>FlashLender: request flash loan(amount)
    FlashLender->>RaindexRouter: onFlashLoan callback
    RaindexRouter->>RaindexRouter: decode payload, _exchange(takeOrders, exchangeData)
    RaindexRouter->>RouteProcessor: processRoute(tokenIn, amountIn, tokenOut, route)
    RouteProcessor->>RaindexRouter: returns swapped amount
    RaindexRouter->>OrderBook: finalize takeOrders with swap results
    RaindexRouter->>FlashLender: repay flash loan
Loading
sequenceDiagram
    participant Router as RaindexRouter
    participant Decoder as Exchange Data Decoder
    participant Processor as Route Processor
    participant Token as ERC20 Token

    Router->>Decoder: decode exchangeData -> RouteLeg[]
    loop per RouteLeg
        alt leg == SUSHI
            Router->>Token: approve Processor(amount)
            Router->>Processor: processRoute(tokenIn, amountIn, tokenOut, route)
            Processor->>Router: return amountOut
            Router->>Router: convert amountOut -> Float, propagate to next leg / takeOrders
            Router->>Token: clear approval
        else
            Router->>Router: revert (not implemented)
        end
    end
    Router->>Router: finalize arb and update IOs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Raindex Router Arb contract' is vague and generic, using non-descriptive terms that don't convey the main change or purpose of the changeset. Consider a more specific title that describes the primary functionality added, such as 'Add Raindex Router for flash-loan arbitrage with external swaps' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2026-01-28-raindex-router-arb

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@rouzwelt rouzwelt changed the base branch from 2025-12-18-exact-swap to main February 4, 2026 21:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@src/abstract/OrderBookV6RaindexRouter.sol`:
- Line 3: The pragma in OrderBookV6RaindexRouter.sol currently reads ^0.8.19
which mismatches the project standard; update the pragma directive at the top of
the file to use the exact compiler version =0.8.25 (replace the existing pragma
line) so the contract (OrderBookV6RaindexRouter) compiles with the project's
foundry.toml setting and remains consistent with other contracts.
- Around line 77-94: Add a check in onFlashLoan to validate that msg.sender is
the trusted lender (e.g., compare msg.sender to a stored sExpectedLender or to
the known orderBook address) and revert if it is not; additionally, in arb4 set
sExpectedLender = address(orderBook) immediately before calling flashLoan and
clear sExpectedLender after the call to avoid reuse. Ensure you update the
onFlashLoan function and the arb4 flow (setting/clearing sExpectedLender around
flashLoan) so the callback only accepts calls from the legitimate lender.
- Around line 125-129: The code accesses takeOrders[0] and takeOrders[1] before
validating the array length, which can cause an out-of-bounds revert; move the
require(takeOrders.length == 2, "Unexpected take orders config length") check to
before any access to takeOrders, then keep the existing empty-order check that
reverts IOrderBookV6.NoOrders() for takeOrders[0].orders.length == 0 ||
takeOrders[1].orders.length == 0; ensure you reference the same symbols
(takeOrders, IOrderBookV6.NoOrders) so the semantics don't change.

In `@src/concrete/arb/RaindexRouterOrderBookV6Arb.sol`:
- Around line 119-122: The fallback() function is not payable so it cannot
receive ETH as the comment implies; either replace it with a receive() external
payable {} if you only need to accept plain ETH transfers, or mark fallback() as
payable (fallback() external payable {}) if you need fallback behavior for
calldata too. Also remove the unused debugging artifact function a(RouteLeg
calldata x) (or, if intended, add a clear purpose/comment and usages) to
eliminate dead code; search for the symbol a and the RouteLeg type to
update/remove accordingly.
- Around line 101-105: The logic in RaindexRouterOrderBookV6Arb.sol that calls
LibDecimalFloat.toFixedDecimalLossy(LibDecimalFloat.FLOAT_ZERO, toTokenDecimals)
and then increments toTokenAmount when lossless==false is incorrect and
unnecessary; replace this block so the minimum output amount is zero
(toTokenAmount = 0) or, if slippage protection is required, compute the minimum
from the expected output (not from FLOAT_ZERO) using the expected output
variable and the appropriate rounding/decimal conversion routine; remove the
conditional increment branch tied to FLOAT_ZERO and the use of lossless in this
context (references: LibDecimalFloat.toFixedDecimalLossy, FLOAT_ZERO,
toTokenAmount, lossless).

In `@test/concrete/arb/RaindexRouterOrderBookV6Arb.t.sol`:
- Line 244: The assertion message is incorrect: update the assertEq call that
checks bobInputVaultBalance (the assertion using assertEq(bobInputVaultBalance,
5e19, "...")) to use a matching comment string such as "expected 50 token C
vault balance for bob input vault" (or equivalent) so the failure message
correctly identifies bob's input vault rather than mentioning Alice's output
vault.
- Around line 314-329: MockSushiRP::processRoute currently does unnecessary
self-approvals and uses transferFrom to move tokens the contract already holds
and ignores the `to` parameter; change it so the contract still pulls tokenIn
from msg.sender via IERC20(tokenIn).transferFrom(msg.sender, address(this),
amountIn) but remove any IERC20(tokenOut).approve calls and replace
IERC20(tokenOut).transferFrom(address(this), msg.sender, amountIn) with
IERC20(tokenOut).transfer(to, amountOut) (or transfer(to, amountIn) to mirror
current return) so tokens are sent to the `to` recipient and no self-approval is
performed. Ensure the function returns the correct amountOut as before.
- Around line 126-245: Add additional focused test functions alongside
testArb4Success to cover edge cases and failure paths: 1) zero-order amounts —
create orders via createOrder with zero quantities and assert router.arb4
reverts or no balances change; 2) token mismatch between legs — build RouteLegs
where encoded tokens don't line up (e.g., route leg uses token pair that doesn't
connect alice/bob orders) and assert arb4 reverts or results in no execution; 3)
swap/flashloan failure — simulate failing SUSHI by deploying a mock that reverts
on expected call and assert router.arb4 reverts and vault balances unchanged; 4)
unsupported RouteLegType — pass an unimplemented RouteLegType and assert revert;
and 5) task validation/access control — pass a TaskV2 with invalid EvaluableV4
(e.g., empty interpreter/store or malformed bytecode) and assert arb4 reverts;
reference testArb4Success, router.arb4, createTakeOrdersConfig,
RouteLeg/RouteLegType, TaskV2, and createOrder to locate where to add each new
test and which assertions to check (revert or specific balance invariants).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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/abstract/OrderBookV6RaindexRouter.sol`:
- Around line 159-160: Guard against a zero flash loan amount by checking the
computed flashLoanAmount (from
IERC20(startTakeOrdersInputToken).balanceOf(address(orderBook)) / 2) before
converting to Float via LibDecimalFloat.fromFixedDecimalLosslessPacked; if
flashLoanAmount == 0 revert with a clear error (e.g., "ZeroFlashLoanAmount") or
return early so you don't attempt a no-op or trigger FlashLoanFailed—update the
logic around the flashLoanAmount variable and any callers that assume a non-zero
Float to handle this short-circuit.

In `@src/concrete/arb/RaindexRouterOrderBookV6Arb.sol`:
- Around line 70-73: After constructing the route, add an explicit assertion
that the token produced by the final leg (prevLegTokenAddress) matches the token
expected by the end order (endTakeOrdersInputToken) before performing the
approvals; specifically, in the section that computes endTakeOrdersInputToken
and then calls IERC20(...).forceApprove(msg.sender, ...), insert a require/check
that prevLegTokenAddress == endTakeOrdersInputToken (with a clear revert
message) so the contract cannot approve the wrong token to the orderbook when
exchangeData/leg ordering is misconfigured.
- Line 87: Remove the stale Slither suppression "slither-disable-next-line
no-unused-vars" that sits above/near the _processSushiLeg usage; since
_processSushiLeg (and its parameters/returns) are used, this no-unused-vars
disable is incorrect and should be deleted (and verify there are no other
unnecessary "no-unused-vars" suppressions around the _processSushiLeg
declaration or its callers); if you intended to suppress a real finding, replace
it with the specific, correct Slither pragma for that particular issue instead
of masking no-unused-vars.

---

Duplicate comments:
In `@src/abstract/OrderBookV6RaindexRouter.sol`:
- Line 3: Update the Solidity pragma to exactly match the project's compiler
version by replacing the current pragma declaration with "pragma solidity
=0.8.25;" so the contract (OrderBookV6RaindexRouter) compiles with the version
specified in foundry.toml; ensure no other files in this contract still use a
different pragma specifier.
- Around line 77-84: The onFlashLoan function currently only checks initiator
and should also validate that msg.sender is the trusted lender; add a check in
onFlashLoan that reverts (reuse BadInitiator or add a new error like BadLender)
if msg.sender != <trusted lender storage variable> (e.g., trustedLender or
lender) before proceeding, so the contract only accepts callbacks from the
authorized lender.

In `@src/concrete/arb/RaindexRouterOrderBookV6Arb.sol`:
- Around line 114-115: The fallback() function is non-payable but the comment
says "Allow receiving gas" (i.e., accept ETH); add a receive() external payable
{} to accept plain ETH transfers (e.g., from WETH unwrap) or make fallback()
payable if you intend arbitrary calldata ETH receipts; update the contract by
adding receive() external payable {} (and leave fallback() as-is or change
fallback() to fallback() external payable {} only if you need fallback to accept
ETH with calldata).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/abstract/OrderBookV6RaindexRouter.sol`:
- Around line 165-173: Capture and check the boolean success flag returned by
orderBook.quote2 instead of discarding it; change the tuple unpacking to (bool
exists, Float maxOutput, Float iORatio) when calling orderBook.quote2 (the call
inside the block that builds QuoteV2 from takeOrders[1].orders[0]) and if exists
is false revert/abort the flow (e.g., revert or skip) so you don't silently
continue with maxOutput == 0 and set takeOrders[0].maximumIO incorrectly; also
remove the //slither-disable-next-line unused-return and, to satisfy the linter,
explicitly reference any intentionally unused return values in a no-op statement
rather than suppressing the warning.
- Around line 178-184: The cap assigned to takeOrders[0].maximumIO is
denominated in startTakeOrdersInputToken (variables maxOutput and
flashLoanAmountFloat) but is applied with IOIsInput = false which limits the
first order's output token—mismatching token denominations; to fix, when the
intention is to limit clearing by the last order's capacity, convert the chosen
minimum (min(maxOutput, flashLoanAmountFloat)) from startTakeOrdersInputToken
into the token units of takeOrders[0]’s output before assigning to
takeOrders[0].maximumIO (preserving IOIsInput = false), taking into account
token decimals and exchange rate/price (use existing normalization/price
conversion helpers or introduce a conversion using the route/order price) so the
cap correctly matches the first order’s output token; alternatively, if you
meant to cap the first order’s input instead, set takeOrders[0].IOIsInput = true
and assign the min directly to maximumIO but document the change.

---

Duplicate comments:
In `@src/abstract/OrderBookV6RaindexRouter.sol`:
- Line 3: The file's Solidity pragma currently uses "^0.8.19" which mismatches
the project standard; update the pragma line in OrderBookV6RaindexRouter.sol to
use the exact compiler version "=0.8.25" (replace the existing "pragma solidity
^0.8.19;" with "pragma solidity =0.8.25;") so it aligns with foundry.toml and
project guidelines.
- Around line 159-160: The computed flashLoanAmount
(IERC20(startTakeOrdersInputToken).balanceOf(address(orderBook)) / 2) can be
zero for balances 0 or 1, so add a guard before calling
LibDecimalFloat.fromFixedDecimalLosslessPacked: read balance into a local (e.g.,
orderBookBalance), compute flashLoanAmount = orderBookBalance / 2, and if
flashLoanAmount == 0 either revert with a clear error (e.g.,
"NoFlashLoanLiquidity") or return/skip the flow; only call
LibDecimalFloat.fromFixedDecimalLosslessPacked(flashLoanAmount,
startInputDecimals) when flashLoanAmount > 0 to avoid no-op arbs or
FlashLoanFailed reverts. Ensure checks reference the variables flashLoanAmount,
orderBookBalance, and the call to LibDecimalFloat.fromFixedDecimalLosslessPacked
so maintainers can locate the fix.
- Around line 78-95: The onFlashLoan callback currently only checks initiator ==
address(this) but fails to validate msg.sender, allowing a malicious lender to
spoof initiator; add a check at the start of onFlashLoan to require msg.sender
== address(orderBook) (or the contract's trusted flash-lender/OrderBook address
variable) and revert if not (use an existing error like BadInitiator or add
BadFlashLender), keeping the existing initiator check and performing this
validation before abi.decode(...) and before calling _exchange(takeOrders,
exchangeData); this ensures only the trusted OrderBook/flash-lender can invoke
onFlashLoan.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/concrete/arb/RaindexRouterOrderBookV6Arb.sol`:
- Around line 104-106: The call to
IRouteProcessor(routeLeg.destination).processRoute currently passes amountOutMin
= 0 which provides no slippage protection; compute a conservative minimum (e.g.,
multiply the expected output derived from prevLegOutputAmount and the
known/quoted rate by a safety factor like 95–99%) and pass that as the minOut
parameter instead of 0, then check the returned amountOut against that minimum
and revert if it is below it; update the call site where processRoute is invoked
(the IRouteProcessor(routeLeg.destination).processRoute(...) call that sets
amountOut) to compute and use amountOutMin from prevLegOutputAmount/expected
rate and enforce the check.
- Around line 59-65: Replace the string reverts for unimplemented RouteLegType
cases with custom errors to save gas: declare a custom error (e.g., error
UnimplementedRouteLeg(RouteLegType legType) or distinct errors like error
RaindexNotImplemented();) and update the checks in
RaindexRouterOrderBookV6Arb.sol where you currently do revert("raindex route leg
type is not yet implemented") / revert("balancer route leg type is not yet
implemented") / revert("stabull route leg type is not yet implemented") to use
revert UnimplementedRouteLeg(leg.routeLegType) or revert RaindexNotImplemented()
etc.; ensure the error is declared at contract or file scope and replace each
string-based revert in the branch handling RouteLegType.RAINDEX,
RouteLegType.BALANCER and RouteLegType.STABULL accordingly.

---

Duplicate comments:
In `@src/concrete/arb/RaindexRouterOrderBookV6Arb.sol`:
- Around line 114-115: The fallback() function is currently non-payable so the
contract cannot accept ETH despite the comment; update the contract to allow
receiving ETH by adding a receive() external payable {} function (or making
fallback() external payable if you prefer to handle calldata) so that WETH
unwraps or refunds do not revert—locate the fallback() definition and either add
the receive() external payable {} or change fallback() to fallback() external
payable {} accordingly.
- Line 87: Remove the stale Slither suppression comment
"//slither-disable-next-line no-unused-vars" because the function
_processSushiLeg uses all its parameters and return values; locate the
suppression immediately above _processSushiLeg and delete that line (or replace
it with a relevant, justified slither rule if another true positive exists) so
the code no longer contains an unnecessary no-unused-vars suppression.
- Around line 70-73: After constructing the route legs, the code independently
reads endTakeOrdersInputToken from takeOrders but never verifies it matches
prevLegTokenAddress (the last swap output), so approvals may be applied to the
wrong token; update the logic in the section using prevLegTokenAddress and
endTakeOrdersInputToken to assert they are equal (e.g.,
require(prevLegTokenAddress == endTakeOrdersInputToken, "mismatched route
token")), or else use prevLegTokenAddress directly for the approve calls
(replace the read of endTakeOrdersInputToken or add a require) so the contract
approves the actual last-leg output token before calling takeOrders4.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/abstract/OrderBookV6RaindexRouter.sol (4)

3-3: Pragma version mismatch — still not addressed.

As per coding guidelines: "Use Solidity compiler version 0.8.25 as specified in foundry.toml"

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

In `@src/abstract/OrderBookV6RaindexRouter.sol` at line 3, The pragma in
OrderBookV6RaindexRouter.sol is using ^0.8.19 but the project requires Solidity
0.8.25; update the pragma directive at the top of the file from ^0.8.19 to
^0.8.25 so the contract (OrderBookV6RaindexRouter) compiles with the version
specified in foundry.toml and matches project guidelines.

159-160: No guard against zero flash loan amount — still unaddressed.

If the orderbook holds 0 or 1 wei of the token, balanceOf / 2 truncates to 0, leading to a wasted-gas no-op or an opaque FlashLoanFailed revert.

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

In `@src/abstract/OrderBookV6RaindexRouter.sol` around lines 159 - 160, The code
computes flashLoanAmount by dividing
IERC20(startTakeOrdersInputToken).balanceOf(address(orderBook)) by 2 then
converts it to a Float; add a guard that checks the raw balance or
flashLoanAmount before calling LibDecimalFloat.fromFixedDecimalLosslessPacked to
avoid a zero flash loan. Specifically, read the balance (via
IERC20(...).balanceOf(address(orderBook))) or use flashLoanAmount and require
flashLoanAmount > 0 (or balance >= 2 if you want to ensure a non-truncated half)
and revert or skip the flash-loan path with a clear error if the check fails,
placing this check before calling LibDecimalFloat.fromFixedDecimalLosslessPacked
and any subsequent flash-loan logic.

178-185: maximumIO cap applied in wrong token denomination — still unaddressed.

Both flashLoanAmountFloat and maxOutput are denominated in startTakeOrdersInputToken, but IOIsInput = false (line 185) means maximumIO caps the output of takeOrders[0], which is the intermediate token. For non-1:1/non-identical-decimals pairs, this constraint is applied in the wrong units.

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

In `@src/abstract/OrderBookV6RaindexRouter.sol` around lines 178 - 185, The cap
for takeOrders[0].maximumIO is being applied using flashLoanAmountFloat and
maxOutput which are denominated in startTakeOrdersInputToken, but since
takeOrders[0].IOIsInput is set to false the cap must be applied in the
intermediate/output token units; update the logic that sets
takeOrders[0].maximumIO (and the comparisons using LibDecimalFloat.gt) to first
convert flashLoanAmountFloat and maxOutput into the takeOrders[0] output
denomination (using the same conversion/price/decimal helper used elsewhere for
startTakeOrders → intermediate conversions), then compare and set maximumIO in
that output unit, and keep IOIsInput = false; ensure you reference and use the
same conversion routine used by the router to avoid mismatched decimals.

78-95: Missing msg.sender validation in onFlashLoan — still unaddressed.

The callback only checks initiator == address(this) but never verifies that msg.sender is the trusted lender (orderbook). A malicious contract could call onFlashLoan directly with initiator = address(this) and attacker-controlled data, causing _exchange to execute with arbitrary decoded parameters.

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

In `@src/abstract/OrderBookV6RaindexRouter.sol` around lines 78 - 95, The
onFlashLoan callback currently only checks initiator but must also verify
msg.sender is the trusted lender; update the onFlashLoan function to require
msg.sender == <trusted lender address/state> (for example an existing state
variable like orderBook or a dedicated lender address set in the contract) and
revert with a clear error (e.g., BadLender(msg.sender) or reuse BadInitiator)
before decoding/dispatching to _exchange; ensure the trusted lender value is
read from the existing state or an accessor (add one if missing) so untrusted
contracts cannot call onFlashLoan with initiator == address(this).
🤖 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/abstract/OrderBookV6RaindexRouter.sol`:
- Around line 175-176: The first explicit call to
IERC20(startTakeOrdersInputToken).forceApprove(address(orderBook), 0) is
redundant because OpenZeppelin's forceApprove already handles the zero-then-set
pattern for non-standard tokens; remove that first call and keep a single
IERC20(startTakeOrdersInputToken).forceApprove(address(orderBook),
type(uint256).max) to set the full allowance for the orderBook (referencing
startTakeOrdersInputToken and orderBook in OrderBookV6RaindexRouter); this
eliminates the wasted gas while preserving correct approval semantics.

---

Duplicate comments:
In `@src/abstract/OrderBookV6RaindexRouter.sol`:
- Line 3: The pragma in OrderBookV6RaindexRouter.sol is using ^0.8.19 but the
project requires Solidity 0.8.25; update the pragma directive at the top of the
file from ^0.8.19 to ^0.8.25 so the contract (OrderBookV6RaindexRouter) compiles
with the version specified in foundry.toml and matches project guidelines.
- Around line 159-160: The code computes flashLoanAmount by dividing
IERC20(startTakeOrdersInputToken).balanceOf(address(orderBook)) by 2 then
converts it to a Float; add a guard that checks the raw balance or
flashLoanAmount before calling LibDecimalFloat.fromFixedDecimalLosslessPacked to
avoid a zero flash loan. Specifically, read the balance (via
IERC20(...).balanceOf(address(orderBook))) or use flashLoanAmount and require
flashLoanAmount > 0 (or balance >= 2 if you want to ensure a non-truncated half)
and revert or skip the flash-loan path with a clear error if the check fails,
placing this check before calling LibDecimalFloat.fromFixedDecimalLosslessPacked
and any subsequent flash-loan logic.
- Around line 178-185: The cap for takeOrders[0].maximumIO is being applied
using flashLoanAmountFloat and maxOutput which are denominated in
startTakeOrdersInputToken, but since takeOrders[0].IOIsInput is set to false the
cap must be applied in the intermediate/output token units; update the logic
that sets takeOrders[0].maximumIO (and the comparisons using LibDecimalFloat.gt)
to first convert flashLoanAmountFloat and maxOutput into the takeOrders[0]
output denomination (using the same conversion/price/decimal helper used
elsewhere for startTakeOrders → intermediate conversions), then compare and set
maximumIO in that output unit, and keep IOIsInput = false; ensure you reference
and use the same conversion routine used by the router to avoid mismatched
decimals.
- Around line 78-95: The onFlashLoan callback currently only checks initiator
but must also verify msg.sender is the trusted lender; update the onFlashLoan
function to require msg.sender == <trusted lender address/state> (for example an
existing state variable like orderBook or a dedicated lender address set in the
contract) and revert with a clear error (e.g., BadLender(msg.sender) or reuse
BadInitiator) before decoding/dispatching to _exchange; ensure the trusted
lender value is read from the existing state or an accessor (add one if missing)
so untrusted contracts cannot call onFlashLoan with initiator == address(this).

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a51bdb and e3e0a21.

📒 Files selected for processing (1)
  • src/abstract/OrderBookV6RaindexRouter.sol

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