Skip to content

Conversation

@lucas-manuel
Copy link
Contributor

@lucas-manuel lucas-manuel commented Jan 6, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Uniswap V4 liquidity pool integration with position minting, adjustment, and swapping capabilities.
    • Introduced configurable tick limit settings for Uniswap V4 pools.
    • Enhanced rate limiting system with improved controls.
  • Bug Fixes

    • Added recipient validation for cross-chain transfers to prevent invalid addresses.
  • Documentation

    • Updated operational requirements and trust assumptions.

✏️ Tip: You can customize this high-level summary in your review settings.

deluca-mike and others added 21 commits November 18, 2025 09:44
* feat: AAVE external lib and Approve internal lib

- cuts down on much needed contract size for future integrations

* fix: PR Review
* feat: UniswapV4 mint/increase/decrease liquidity support (SC-1234)

* fix: PR review

* fix: literal amount assertions

* fix: PR Review
* feat: UniswapV4 swap support (SC-1249)

* fix: PR review

* fix: PR Review 2

* fix: build workflow
* feat: AAVE external lib and Approve internal lib (SC-1182) (#162)

* feat: AAVE external lib and Approve internal lib

- cuts down on much needed contract size for future integrations

* fix: PR Review

* feat: Add ERC4626 lib (SC-1233) (#188)

* add

* fix: Use relative paths (#191)

---------

Co-authored-by: Michael De Luca <michael.deluca@circle-free.com>
Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
* UniswapV4TestBase

* use UniswapV4TestBase

* refactor for generics

* USDT/USDS pool tests
* test: UniswapV4 fuzz tests (SC-1250)

* format and usdt-usds fuzz tests

* fix: fuzz runs instructions

---------

Co-authored-by: Michael De Luca <michael.deluca@circle-free.com>
* feat: Apply Tick Checks on `increaseLiquidityUniswapV4` (SC-1262)

* fix: PR Review

* fix: PR Review

* fix: PR Review

* fix: remove extra line
* feat: UniswapV4 swap invalid tokenIn clear error (SC-1259)

* fix: PR review
…C-1277) (#200)

* feat: rm warning and add check

* fix: explicit casting

* fix: update maxAmount test

* fix: new lines
Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
…Position (#204)

Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
Co-authored-by: supercontracts <bonjour.tabish@gmail.com>
…pproveWithPermit2 ` (#207)

* fix: check amount for permit2 C13

* fix: Pr Review

* fix

---------

Co-authored-by: Michael De Luca <michael.deluca@circle-free.com>
* feat: add additional requirements/assumptions

* fix: typo

---------

Co-authored-by: Tabish Shaikh <bonjour.tabish@gmail.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Walkthrough

This PR integrates Uniswap V4 support via submodules, introduces new library abstractions (ApproveLib, ERC4626Lib, AaveLib, UniswapV4Lib), refactors approval and rate-limiting patterns, removes Morpho allocation functions, updates deployment configs, and adds recipient validation for cross-chain transfers.

Changes

Cohort / File(s) Summary
Dependencies & Submodules
.gitmodules, lib/uniswap-v4-core, lib/uniswap-v4-periphery
Added Uniswap V4 core and periphery as git submodules; updated submodule commit pointers.
Build Configuration
foundry.toml, .github/workflows/ci.yml
Added Uniswap V4 core remapping; updated CI build step to use forge build --sizes ./src instead of --skip test --sizes.
Deployment Config
script/input/1/*-production.json (arbitrum_one, avalanche, optimism, unichain)
Added two new top-level fields (almProxy, rateLimits) to each network config file.
Documentation
README.md
Added Trust Assumptions for Ethena delegated signer role and Uniswap V4 1:1 asset requirement; added corresponding Operational Requirements note.
New Library: ApproveLib
src/libraries/ApproveLib.sol
Introduced internal helper for token approvals via ALM proxy with retry logic on failure.
New Library: ERC4626Lib
src/libraries/ERC4626Lib.sol
Introduced library for rate-limited ERC4626 deposit/withdrawal/redemption with max-exchange-rate gating and EXCHANGE_RATE_PRECISION constant.
New Library: AaveLib
src/libraries/AaveLib.sol
Introduced library for Aave V3 deposits/withdrawals with slippage enforcement and rate-limit key management.
New Library: UniswapV4Lib
src/libraries/UniswapV4Lib.sol
Introduced comprehensive library for Uniswap V4 liquidity operations (mint, increase, decrease positions; swaps) with tick-limit validation, Permit2-based approvals, and rate limiting.
Interface Files
src/interfaces/Common.sol, src/interfaces/UniswapV4.sol
Added IERC20Like, IPermit2Like interfaces; added IPositionManagerLike, IUniversalRouterLike interfaces for Uniswap V4 integration.
Library Updates: Imports & Approval Refactoring
src/libraries/CCTPLib.sol, src/libraries/CurveLib.sol, src/libraries/PSMLib.sol
Updated IERC20 import paths to local lib location; replaced internal _approve calls with ApproveLib.approve in CurveLib.
RateLimitHelpers Refactoring
src/libraries/RateLimitHelpers.sol
Renamed parameters for clarity (asseta), replaced makeUint32Key with distinct makeBytes32Key and updated makeUint32Key.
ForeignController Refactoring
src/ForeignController.sol
Added onlyRole(DEFAULT_ADMIN_ROLE) to setMaxExchangeRate; wrapped transferAsset with rate limiting; added onlyRole(RELAYER) and recipient validation to transferTokenLayerZero; removed Morpho functions (setSupplyQueueMorpho, updateWithdrawQueueMorpho, reallocateMorpho) and internal _rateLimited helper.
MainnetController Expansion
src/MainnetController.sol
Added uniswapV4TickLimits mapping and setUniswapV4TickLimits function; added four Uniswap V4 operations (mintPositionUniswapV4, increaseLiquidityUniswapV4, decreaseLiquidityUniswapV4, swapUniswapV4); replaced EXCHANGE_RATE_PRECISION constant with public function; refactored ERC4626/Aave flows to use library wrappers; replaced _approve calls with ApproveLib.approve.
Test Suite: Morpho Removal
test/base-fork/MorphoAllocations.t.sol
Removed entire Morpho test suite including base class and test contracts exercising Morpho operations.
Test Suite: Approval & LayerZero
test/mainnet-fork/Approve.t.sol, test/mainnet-fork/LayerZero.t.sol
Updated approval tests to use ApproveLib; added test_transferTokenLayerZero_recipientNotSet tests to verify recipient validation; updated test setup to configure LayerZero recipients.
Test Suite: Misc Updates
test/mainnet-fork/Curve.t.sol, test/mainnet-fork/OTCSwaps.t.sol, test/unit/controllers/Admin.t.sol
Removed IERC4626 import (Curve.t.sol); removed OTC import (OTCSwaps.t.sol); added MainnetControllerSetUniswapV4TickLimitsTests with tick-limit validation and access-control tests (Admin.t.sol).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainnetController
    participant UniswapV4Lib
    participant RateLimitHelpers
    participant ALMProxy
    participant PositionManager

    User->>MainnetController: mintPositionUniswapV4(poolId, tickLower, tickUpper, liquidity, ...)
    MainnetController->>UniswapV4Lib: mintPosition(proxy, rateLimits, poolId, ...)
    
    rect rgb(200, 220, 255)
        Note over UniswapV4Lib: Validation Phase
        UniswapV4Lib->>UniswapV4Lib: _checkTickLimits(tickLower, tickUpper, ...)
        UniswapV4Lib->>UniswapV4Lib: _getPoolKeyFromPoolId(poolId)
    end
    
    rect rgb(200, 255, 220)
        Note over UniswapV4Lib: Rate Limit & Approval Phase
        UniswapV4Lib->>RateLimitHelpers: makeAddressKey(LIMIT_DEPOSIT, token)
        RateLimitHelpers-->>UniswapV4Lib: rateLimitKey
        UniswapV4Lib->>RateLimitHelpers: decreaseRateLimit(rateLimitKey, amount)
        UniswapV4Lib->>UniswapV4Lib: _approveWithPermit2(token0, token1, proxy)
    end
    
    rect rgb(255, 220, 200)
        Note over UniswapV4Lib,ALMProxy: Execution Phase
        UniswapV4Lib->>UniswapV4Lib: _getMintCalldata(poolKey, tickLower, tickUpper, liquidity, ...)
        UniswapV4Lib->>ALMProxy: doCall(PositionManager, mintCalldata)
        ALMProxy->>PositionManager: modifyLiquidities(unlockData, deadline)
        PositionManager-->>ALMProxy: success
        ALMProxy-->>UniswapV4Lib: encoded tokenId
    end
    
    UniswapV4Lib-->>MainnetController: tokenId
    MainnetController-->>User: tokenId
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • deluca-mike
  • supercontracts

🐰 Hops of joy through the code,
V4 swaps and liquidity deployed,
Rate limits now guard each flow,
Libraries tidy the proxy's way,
One blessed merge makes rabbits play! 🌱✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add v1.9.0 release' is concise, descriptive, and follows the required format with a single-word prefix and colon. It accurately reflects the release-focused nature of the changes across the entire changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

Coverage after merging dev into master will be

99.36%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
deploy
   ControllerDeploy.sol100%100%100%100%
   ForeignControllerInit.sol100%100%100%100%
   MainnetControllerInit.sol97.37%93.33%100%100%152, 90
src
   ALMProxy.sol100%100%100%100%
   ALMProxyFreezable.sol100%100%100%100%
   ForeignController.sol94.90%84.62%95.65%97.22%128–129, 129, 129, 316–317, 573
   MainnetController.sol99.18%100%98.28%99.24%538–539
   OTCBuffer.sol100%100%100%100%
   RateLimitHelpers.sol100%100%100%100%
   RateLimits.sol100%100%100%100%
src/libraries
   AaveLib.sol100%100%100%100%
   ApproveLib.sol100%100%100%100%
   CCTPLib.sol100%100%100%100%
   CurveLib.sol100%100%100%100%
   ERC4626Lib.sol96%75%100%100%108
   PSMLib.sol100%100%100%100%
   UniswapV4Lib.sol99.33%96%100%100%284

Copy link

@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 @README.md:
- Line 150: Add programmatic validation in setUniswapV4TickLimits to enforce the
README's 1:1 asset requirement: before accepting tick limits, verify token0 and
token1 represent equal-valued assets (e.g., compare their canonical asset IDs or
use the same oracle/price feed to ensure identical USD value per normalized
unit) and revert with a clear error if they differ; update any callers/tests
accordingly and remove the reliance on the comment in _increaseLiquidity by
making this guarantee upfront in setUniswapV4TickLimits.

In @script/input/1/arbitrum_one-production.json:
- Around line 9-11: Reorder the JSON object fields so they match the ordering
used in other production configs: place "rateLimits" before "almProxy" (keeping
"susds" as-is); update the entry that currently lists "almProxy" then
"rateLimits" to instead list "rateLimits" then "almProxy" for consistency across
configs.

In @src/libraries/CCTPLib.sol:
- Line 4: The import in CCTPLib.sol uses a relative path to OpenZeppelin instead
of the project remapping; update the import line that references IERC20 to use
the remapped path "@openzeppelin/contracts/token/ERC20/IERC20.sol" (replace the
current "../../lib/openzeppelin-contracts/..." import) so the IERC20 import uses
the configured foundry remapping for consistency and maintainability.

In @src/libraries/PSMLib.sol:
- Line 4: Replace the relative OpenZeppelin import with the remapped path:
update the import in PSMLib.sol that currently references
../../lib/openzeppelin-contracts/... to use
@openzeppelin/contracts/token/ERC20/IERC20.sol so the IERC20 symbol is imported
via the configured Foundry remapping for consistency and future-proofing.

In @src/libraries/UniswapV4Lib.sol:
- Around line 253-298: In _approveWithPermit2, replace the unchecked
proxy.call(...) reset with an explicit (bool success, bytes memory data) =
proxy.call(...) and, if success is false, emit a non-reverting event (e.g.,
Permit2ResetFailed(address proxy, address token, bytes data)) so failures (like
out-of-gas or unexpected reverts) are recorded for debugging while preserving
the original non-fatal behavior; add the event declaration and emit it when
success==false, but do not change the current non-revert semantics that the
comment intends.
- Around line 30-35: The three addresses _PERMIT2, _POSITION_MANAGER, and
_ROUTER are hardcoded to mainnet, which prevents multi-chain deployment; replace
these internal constant addresses with configurable values (e.g.,
constructor/initializer-set immutable variables or function parameters) or route
lookups through a registry contract so the library can be reused across
networks, and if you keep them constant add a clear comment that UniswapV4Lib is
mainnet-only. Update all usages of _PERMIT2, _POSITION_MANAGER, and _ROUTER to
read the new configurable variables or registry lookups.
- Around line 349-382: In _decreaseLiquidity, avoid underflow when computing
endingBalance0 - startingBalance0 and endingBalance1 - startingBalance1 by first
computing deltas safely: for each token compute delta as ending >= starting ?
ending - starting : 0 (or revert with a clear require if you prefer
failing-fast); then pass those non-negative deltas to _getNormalizedBalance and
use the sum for rateLimitDecrease before calling
IRateLimits.triggerRateLimitDecrease on the RateLimit key. Ensure the fix is
applied to the function _decreaseLiquidity and update the inline comment to
reflect the defensive check.
📜 Review details

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59ce121 and 16525ad.

⛔ Files ignored due to path filters (2)
  • audits/v190-cantina-audit.pdf is excluded by !**/*.pdf
  • audits/v190-certora-audit.pdf is excluded by !**/*.pdf
📒 Files selected for processing (29)
  • .github/workflows/ci.yml
  • .gitmodules
  • README.md
  • foundry.toml
  • lib/uniswap-v4-core
  • lib/uniswap-v4-periphery
  • script/input/1/arbitrum_one-production.json
  • script/input/1/avalanche-production.json
  • script/input/1/optimism-production.json
  • script/input/1/unichain-production.json
  • src/ForeignController.sol
  • src/MainnetController.sol
  • src/RateLimitHelpers.sol
  • src/interfaces/Common.sol
  • src/interfaces/UniswapV4.sol
  • src/libraries/AaveLib.sol
  • src/libraries/ApproveLib.sol
  • src/libraries/CCTPLib.sol
  • src/libraries/CurveLib.sol
  • src/libraries/ERC4626Lib.sol
  • src/libraries/PSMLib.sol
  • src/libraries/UniswapV4Lib.sol
  • test/base-fork/MorphoAllocations.t.sol
  • test/mainnet-fork/Approve.t.sol
  • test/mainnet-fork/Curve.t.sol
  • test/mainnet-fork/LayerZero.t.sol
  • test/mainnet-fork/OTCSwaps.t.sol
  • test/mainnet-fork/Uniswapv4.t.sol
  • test/unit/controllers/Admin.t.sol
💤 Files with no reviewable changes (3)
  • test/mainnet-fork/Curve.t.sol
  • test/mainnet-fork/OTCSwaps.t.sol
  • test/base-fork/MorphoAllocations.t.sol
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-11-04T17:15:21.481Z
Learnt from: deluca-mike
Repo: sparkdotfi/spark-alm-controller PR: 185
File: test/base-fork/Aave.t.sol:59-63
Timestamp: 2025-11-04T17:15:21.481Z
Learning: In the spark-alm-controller repository, test files in test/base-fork/ use ForkTestBase from test/base-fork/ForkTestBase.t.sol where _setControllerEntered() defaults to writing the reentrancy guard state to foreignController, while test files in test/mainnet-fork/ use ForkTestBase from test/mainnet-fork/ForkTestBase.t.sol where _setControllerEntered() defaults to mainnetController.

Applied to files:

  • test/mainnet-fork/Approve.t.sol
  • test/mainnet-fork/LayerZero.t.sol
  • test/unit/controllers/Admin.t.sol
📚 Learning: 2025-11-04T17:15:39.489Z
Learnt from: deluca-mike
Repo: sparkdotfi/spark-alm-controller PR: 185
File: test/base-fork/Morpho.t.sol:122-126
Timestamp: 2025-11-04T17:15:39.489Z
Learning: In the spark-alm-controller repository, tests in the `test/base-fork/` directory use `ForkTestBase` from `test/base-fork/ForkTestBase.t.sol`, where the `_setControllerEntered()` helper function defaults to setting the reentrancy guard state on `foreignController`. Tests in `test/mainnet-fork/` use their own `ForkTestBase` which defaults to `mainnetController`.

Applied to files:

  • test/mainnet-fork/Approve.t.sol
  • test/mainnet-fork/LayerZero.t.sol
  • test/unit/controllers/Admin.t.sol
📚 Learning: 2025-11-11T19:43:16.032Z
Learnt from: deluca-mike
Repo: sparkdotfi/spark-alm-controller PR: 0
File: :0-0
Timestamp: 2025-11-11T19:43:16.032Z
Learning: In spark-alm-controller, the burnPosition and decreasePosition functions in UniswapV4Lib.sol intentionally do not validate ownership because burning someone else's position would cause the underlying tokens to flow to the proxy, which benefits the system rather than harming it.

Applied to files:

  • test/mainnet-fork/Approve.t.sol
  • src/libraries/ApproveLib.sol
  • src/interfaces/UniswapV4.sol
  • src/libraries/UniswapV4Lib.sol
  • src/libraries/CurveLib.sol
  • src/MainnetController.sol
📚 Learning: 2025-10-16T16:47:34.627Z
Learnt from: supercontracts
Repo: sparkdotfi/spark-alm-controller PR: 170
File: src/MainnetController.sol:1153-1181
Timestamp: 2025-10-16T16:47:34.627Z
Learning: In src/MainnetController.sol, the USDS token always returns true on successful transfer operations, so it does not require the empty return data handling that some non-standard ERC20 tokens (like USDT) need.

Applied to files:

  • test/mainnet-fork/Approve.t.sol
  • test/mainnet-fork/LayerZero.t.sol
  • src/MainnetController.sol
📚 Learning: 2025-10-16T17:40:33.927Z
Learnt from: supercontracts
Repo: sparkdotfi/spark-alm-controller PR: 170
File: test/mocks/Mocks.sol:25-134
Timestamp: 2025-10-16T17:40:33.927Z
Learning: In the spark-alm-controller repository, test mock contracts like MockTokenkReturnNull do not need to include ERC20 standard events (Transfer, Approval) for testing purposes. Full ERC20 compliance with events is not required for test mocks.

Applied to files:

  • test/mainnet-fork/LayerZero.t.sol
📚 Learning: 2025-11-11T19:43:16.032Z
Learnt from: deluca-mike
Repo: sparkdotfi/spark-alm-controller PR: 0
File: :0-0
Timestamp: 2025-11-11T19:43:16.032Z
Learning: In spark-alm-controller, rate limits for Uniswap positions sum both tokens normalized to 18 decimals, which doesn't account for actual USD values. This is a known design decision where normalized balance is used rather than USD-denominated values.

Applied to files:

  • test/mainnet-fork/LayerZero.t.sol
  • README.md
  • src/libraries/ERC4626Lib.sol
  • src/libraries/UniswapV4Lib.sol
  • src/MainnetController.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: coverage
  • GitHub Check: test
  • GitHub Check: build
🔇 Additional comments (64)
.github/workflows/ci.yml (1)

25-25: Build command change is correct.

The modification from --skip test to explicitly specifying ./src is cleaner and more maintainable. All production contracts reside exclusively in the src/ directory as configured in foundry.toml, and no source files exist outside this directory that need to be built. Deployment and script files are properly separated into their own directories. Submodule dependencies are correctly configured via remappings and will resolve as needed when referenced from src/ contracts.

script/input/1/arbitrum_one-production.json (1)

9-11: Verify the production addresses for Arbitrum One before merging.

The new almProxy (0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709) and rateLimits (0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838) addresses must be validated against the Arbitrum One network and cross-referenced within the project to ensure correctness.

script/input/1/unichain-production.json (1)

9-11: Verify the production addresses for Unichain.

The new rateLimits and almProxy addresses are being added to the production configuration. Given the "Priority: High" label, ensure these addresses are correct for the Unichain network before merging.

script/input/1/optimism-production.json (1)

9-11: Verify these new Optimism production addresses against official sources.

The rateLimits (0x6B34A6B84444dC3Fc692821D5d077a1e4927342d) and almProxy (0x876664f0c9Ff24D1aa355Ce9f1680AE1A5bf36fB) addresses are new to the codebase with no internal references to cross-check. Confirm these addresses are correct for the Optimism network by validating them against official Optimism contract documentation before merging.

script/input/1/avalanche-production.json (1)

7-9: Verify the production addresses for Avalanche with the deployment team.

The new rateLimits and almProxy addresses are being added to the production configuration. While both contract names are documented in the README as legitimate components of the ALM architecture, the actual addresses (0xb79972e8B21f0dE911E65AC342ac85ad38C9A77a for rateLimits and 0xecE6B0E8a54c2f44e066fBb9234e7157B15b7FeC for almProxy) must be confirmed with the team that deployed these contracts on Avalanche before merging.

lib/uniswap-v4-periphery (1)

1-1: The submodule commit is valid and properly configured. Verification confirms commit 3779387e5d296f39df543d23524b050f89a62917 exists in the Uniswap/v4-periphery repository, is reachable from the main branch, and has legitimate authorship (Daniel Gretzke, "Add per hop slippage checks (#497)"). No compatibility or security concerns identified.

lib/uniswap-v4-core (1)

1-1: The submodule commit e50237c43811bd9b526eff40f26772152a42daba is correctly pinned to the v4.0.0 tagged release of the Uniswap V4-Core repository, ensuring proper reproducibility and stability for this production release.

foundry.toml (1)

21-21: LGTM!

The Uniswap V4 core remapping is correctly formatted and consistent with other remappings in the configuration. This properly resolves the @uniswap/v4-core import prefix to the submodule location.

README.md (1)

143-143: Verify that Ethena's external validation safeguards are sufficient.

This trust assumption introduces a dependency on Ethena's external Order Validity Checks to prevent a compromised RELAYER from setting a malicious delegated signer. Ensure that:

  1. Ethena's validation mechanisms are well understood and documented
  2. The risk of relying on external validation is acceptable to governance
  3. There are fallback mechanisms if Ethena's checks fail or are bypassed

Do you want to add additional smart contract-level constraints to limit the risk, or document the specific Ethena validation rules that are relied upon?

src/interfaces/Common.sol (1)

1-18: LGTM! Clean minimal interface definitions.

These interfaces provide focused abstractions for ERC20-like and Permit2-like interactions. The intentionally minimal function sets (e.g., IERC20Like with only approve, balanceOf, and decimals) are appropriate for the specific use cases in the codebase, avoiding unnecessary dependencies on full interface definitions. The "Like" suffix naming convention clearly indicates these are simplified interfaces.

src/libraries/AaveLib.sol (1)

22-57: LGTM! Deposit logic is sound.

The function correctly:

  • Rate-limits before the deposit operation
  • Validates slippage parameters
  • Tracks aToken balance changes to measure actual tokens received
  • Enforces slippage bounds using the standard maxSlippage multiplier pattern (where 1e18 = 100%)
.gitmodules (1)

50-55: LGTM! Uniswap V4 submodule additions.

The submodule configuration for uniswap-v4-periphery and uniswap-v4-core is correct and references the official Uniswap repositories.

test/mainnet-fork/Approve.t.sol (1)

9-10: LGTM! Test refactoring to use ApproveLib.

The test file correctly adopts ApproveLib.approve in place of the previous internal _approve calls, and the expected revert message now matches ApproveLib's "MC/approve-failed" error string.

Also applies to: 64-69, 269-269

src/libraries/ApproveLib.sol (1)

11-35: LGTM! USDT-compatible approval implementation.

The function correctly handles non-standard ERC20 tokens (like USDT) that require resetting allowance to zero before setting a new value:

  1. Lines 15-16: Uses low-level call to attempt the approval without reverting on failure
  2. Lines 20-26: Checks if the approve succeeded (empty return data or true)
  3. Lines 29-31: If the first attempt failed, resets to zero and retries using typed interface calls that revert on failure
  4. Line 34: Final validation ensures the approval succeeded

The mixing of low-level and typed calls is intentional—the first call must not revert to handle tokens that fail on non-zero allowances, while subsequent calls should revert to catch genuine errors.

src/libraries/CurveLib.sol (1)

4-4: LGTM! Refactoring to use ApproveLib.

The file correctly adopts ApproveLib.approve for token approvals in both swap and addLiquidity functions. The import path update for IERC20 and the addition of the address(params.proxy) cast are appropriate for the new library-based approval pattern.

Also applies to: 9-10, 118-123, 159-164

src/RateLimitHelpers.sol (4)

6-8: LGTM!

The generic parameter name a improves reusability across different use cases beyond just assets.


10-12: LGTM!

The generic parameter names improve reusability for various address-pair key combinations.


14-16: LGTM!

This new helper provides support for bytes32-based key generation, maintaining consistency with the existing pattern.


18-20: LGTM!

The generic parameter name improves reusability beyond just domain identifiers.

src/interfaces/UniswapV4.sol (3)

4-7: LGTM!

The imports correctly reference Uniswap V4 core and periphery types from the submodules.


9-21: LGTM!

The interface correctly defines the Uniswap V4 Position Manager functions needed for liquidity management operations.


23-27: LGTM!

The interface correctly defines the Universal Router execute function for command-based operations.

test/mainnet-fork/LayerZero.t.sol (4)

63-86: LGTM!

The addition of recipient setup (lines 76-79) ensures this test properly focuses on the zero-maxAmount error without hitting the new recipient-not-set validation.


141-182: LGTM!

This test correctly validates the new recipient-not-set safety check, ensuring LayerZero transfers cannot proceed without a configured recipient.


427-450: LGTM!

The addition of recipient setup (lines 440-443) ensures this test properly focuses on the zero-maxAmount error, consistent with the mainnet controller test.


508-549: LGTM!

This test correctly validates the new recipient-not-set safety check for the foreign controller, mirroring the mainnet controller test.

test/unit/controllers/Admin.t.sol (6)

4-4: LGTM!

The IAccessControl import is correctly added to support access control error checking in the new tests.


444-446: LGTM!

The test constants are properly defined for the Uniswap V4 tick limits test scenarios.


448-452: LGTM!

The reentrancy test correctly validates that the function is protected against reentrancy attacks.


454-465: LGTM!

The access control test correctly validates that only admins can set Uniswap V4 tick limits.


467-481: LGTM!

The test correctly validates tick parameter constraints: lower must be less than upper, and maxTickSpacing can only be zero if all parameters are zero.


483-499: LGTM!

The success test correctly validates event emission, reentrancy guard behavior, and state updates for setting Uniswap V4 tick limits.

src/libraries/ERC4626Lib.sol (5)

15-15: LGTM!

The exchange rate precision constant is appropriately set for high-precision calculations.


17-43: LGTM!

The deposit function correctly implements rate-limited ERC4626 deposits with exchange rate validation. The flow properly approves the underlying asset, executes the deposit via the proxy, and enforces the maximum exchange rate constraint.


45-72: LGTM!

The withdraw function correctly implements bidirectional rate limiting: it decreases the withdraw limit (consuming capacity) and increases the deposit limit (freeing capacity). The ERC4626 withdraw call is properly structured.


74-101: LGTM!

The redeem function correctly implements bidirectional rate limiting based on the actual assets received. The rate limit adjustments properly consume withdraw capacity and free deposit capacity.


103-111: No overflow protection needed—Solidity 0.8.21 automatically reverts on arithmetic overflow by default. The calculation (EXCHANGE_RATE_PRECISION * assets) / shares is already protected by the language and requires no additional safeguards.

Likely an incorrect or invalid review comment.

src/ForeignController.sol (3)

163-172: LGTM!

The setMaxExchangeRate function properly uses the onlyRole(DEFAULT_ADMIN_ROLE) modifier, which is consistent with other admin functions in this contract like setMaxSlippage, setMintRecipient, and setLayerZeroRecipient. The zero-address check and event emission pattern are correct.


187-205: LGTM!

The transferAsset function refactor using the rateLimited modifier is cleaner than an inline call. The rate limit key construction using RateLimitHelpers.makeAddressAddressKey correctly combines asset and destination for granular control.


295-343: Good security improvement with recipient validation.

The addition of explicit recipient lookup and validation (lines 309-311) before use in SendParam is a solid security enhancement. This prevents transfers to unconfigured destinations that would otherwise result in lost funds.

src/MainnetController.sol (16)

91-97: LGTM!

Moving the OTC struct declaration inside the contract is a reasonable organizational change that keeps the data structure close to its usage.


133-138: LGTM!

The UniswapV4TickLimitsSet event properly captures all relevant parameters for tracking tick limit configuration changes, with poolId correctly indexed for efficient filtering.


163-165: LGTM!

Using constants from UniswapV4Lib ensures consistency between the controller and library. The uniswapV4TickLimits mapping provides per-pool tick configuration as expected.

Also applies to: 207-209


310-321: LGTM!

Delegating exchange rate calculation to ERC4626Lib.getExchangeRate centralizes this logic and ensures consistency across the codebase.


323-346: Verify the tick limit validation allows intended edge cases.

The validation logic on lines 333-337 allows either:

  1. All zeros (to clear/disable limits), OR
  2. maxTickSpacing > 0 AND tickLowerMin < tickUpperMax

This means tickLowerMin == tickUpperMax with maxTickSpacing > 0 is rejected, which is correct since no valid range could exist. However, confirm that negative tick values are intentionally supported (e.g., tickLowerMin = -100, tickUpperMax = 100), which is valid for Uniswap V4 pools centered around tick 0.


493-506: LGTM!

The refactor to ERC4626Lib.deposit properly passes all required parameters including maxExchangeRate for slippage protection. Named parameters improve readability.


508-536: LGTM!

Both withdrawERC4626 and redeemERC4626 correctly delegate to their respective library functions with appropriate rate limit IDs for both withdraw and deposit (for the refill mechanism).


538-540: LGTM!

The EXCHANGE_RATE_PRECISION function provides backward compatibility for external callers that may have relied on the previously-removed constant.


546-572: LGTM!

The Aave deposit/withdraw functions are cleanly refactored to use AaveLib, maintaining the same functionality while improving code organization.


643-666: LGTM!

The mintPositionUniswapV4 function correctly passes the uniswapV4TickLimits mapping storage reference to the library for tick validation. All parameters are properly forwarded.


668-689: LGTM!

The increaseLiquidityUniswapV4 function properly includes tick limits validation through the library, which is important since positions could have been minted under different constraints or transferred to the proxy.


691-711: LGTM!

The decreaseLiquidityUniswapV4 function correctly omits tick limits since decreasing liquidity doesn't require position bounds validation. Based on learnings, ownership validation is intentionally omitted as decreasing another account's position benefits the system.


760-761: LGTM!

Using ApproveLib.approve for Ethena mint/burn preparation is consistent with the library-based approval pattern adopted throughout this PR.

Also applies to: 766-767


929-931: Good security improvement with recipient validation.

This mirrors the same improvement made in ForeignController.sol. Validating the recipient is configured before proceeding prevents transfers to zero addresses.


953-953: LGTM!

The quoteOFT destructuring with explicit spacing ( , , OFTReceipt memory receipt ) is consistent with the updated return shape and matches the pattern in ForeignController.sol.


713-732: The casting pattern is confirmed to be intentional and correctly used.

The maxSlippages mapping is keyed by address, and Uniswap V4 poolIds are designed such that their lower 160 bits represent the pool identifier. The cast address(uint160(uint256(poolId))) is the standard way to derive the pool address for slippage lookups. This pattern is consistently used throughout the test suite (see test/mainnet-fork/Uniswapv4.t.sol) when configuring maxSlippage for V4 pools.

src/libraries/UniswapV4Lib.sol (8)

41-80: LGTM!

The mintPosition function properly validates tick limits before minting, constructs the pool key from poolId, verifies the match, and delegates to _increaseLiquidity for the actual operation and rate limiting.


82-127: LGTM!

The increasePosition function correctly:

  1. Validates proxy ownership of the position (line 95-98)
  2. Re-validates tick limits against current constraints (important for positions minted under old limits or transferred in)
  3. Verifies poolId matches the position's actual pool

129-164: LGTM!

The decreasePosition function intentionally omits ownership validation. Based on learnings, this is by design: burning/decreasing another account's position would send tokens to the proxy, which benefits the system rather than harming it.


166-247: LGTM with minor observation.

The swap function correctly:

  1. Validates maxSlippage is configured
  2. Validates tokenIn is one of the pool's currencies
  3. Applies rate limit based on normalized input amount
  4. Validates amountOutMin meets slippage requirements
  5. Resets Permit2 approval after swap

The rate limit decrease uses amountIn rather than actual amount taken (noted in comment line 190), which is a conservative approach.


300-347: LGTM!

The _increaseLiquidity function correctly:

  1. Sets up Permit2 approvals for both tokens
  2. Measures token balance changes to compute actual amounts used
  3. Uses _clampedSub to handle edge cases where tokens might be received
  4. Resets approvals after operation (good security practice)

The normalized balance approach for rate limiting is a known design decision per learnings.


388-400: LGTM!

The _checkTickLimits function provides comprehensive validation:

  1. Ensures tick limits are configured (maxTickSpacing != 0)
  2. Validates tick ordering
  3. Validates ticks are within bounds
  4. Validates tick range doesn't exceed maximum spacing

522-526: Potential precision loss for tokens with >18 decimals.

The normalization balance * 1e18 / (10 ** decimals) will lose precision for tokens with more than 18 decimals (e.g., if decimals = 24, dividing by 10^24 then multiplying by 1e18 loses 6 decimal places of precision).

Per learnings, this is a known design decision where normalized balance is used rather than USD-denominated values. Document this limitation if not already done.


544-546: Implementation is correct. The _requirePoolIdMatch function correctly computes poolId as keccak256(abi.encode(poolKey)), which matches Uniswap V4's standard. The PoolKey structure is imported from the official Uniswap V4 core library, ensuring proper field ordering and encoding.

- All ERC-4626 vaults that are onboarded MUST have an initial burned shares amount that prevents rounding-based frontrunning attacks. These shares have to be unrecoverable so that they cannot be removed at a later date.
- All ERC-20 tokens are to be non-rebasing with sufficiently high decimal precision.
- Rate limits must be configured for specific ERC-4626 vaults and AAVE aTokens (vaults without rate limits set will revert). Unlimited rate limits can be used as an onboarding tool.
- All Uniswap V4 pool onboardings are to be done with 1:1 assets.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, understand the repo structure
find . -type f -name "*.sol" | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 701


🏁 Script executed:

#!/bin/bash
# Search for Uniswap V4 onboarding/configuration functions
rg -nP -C5 '(setUniswapV4|addUniswapV4|configureUniswapV4|onboard.*[Uu]niswap.*[Vv]4)' --type=sol

# Also search more broadly for Uniswap V4 related functions
rg -nP 'UniswapV4|uniswapv4' --type=sol -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 130


🏁 Script executed:

#!/bin/bash
# Search for any validation logic checking asset parity, 1:1 ratios, or equal assets
rg -nP -C3 '(require.*1:1|require.*equal.*asset|require.*parity|asset.*ratio|token.*token|decimal)' --type=sol | head -100

Repository: sparkdotfi/spark-alm-controller

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Search for Uniswap V4 related code in all files
rg -nP '(UniswapV4|uniswapv4)' -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 33697


🏁 Script executed:

#!/bin/bash
# Search for 1:1 asset or equal token validation
rg -nP -C3 '(1:1|equal.*token|token.*equal|require.*asset)' | head -150

Repository: sparkdotfi/spark-alm-controller

Length of output: 11041


🏁 Script executed:

#!/bin/bash
# Look at the README to see what it says about the 1:1 constraint
rg -nP -C5 '1:1.*asset|Uniswap.*V4.*onboard' -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 1006


🏁 Script executed:

#!/bin/bash
# Read UniswapV4Lib.sol to understand the implementation
wc -l src/libraries/UniswapV4Lib.sol
head -100 src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 4025


🏁 Script executed:

#!/bin/bash
# Read the setUniswapV4TickLimits function
sed -n '323,350p' src/MainnetController.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 1129


🏁 Script executed:

#!/bin/bash
# Search for any validation that checks if assets are equal or 1:1 in Uniswap V4
rg -n 'token0|token1|asset' src/libraries/UniswapV4Lib.sol -A 3 -B 1 | head -80

Repository: sparkdotfi/spark-alm-controller

Length of output: 3283


🏁 Script executed:

#!/bin/bash
# Look for _getPoolKeyFromPoolId and _requirePoolIdMatch to see if there's validation
grep -n '_getPoolKeyFromPoolId\|_requirePoolIdMatch' src/libraries/UniswapV4Lib.sol -A 5

Repository: sparkdotfi/spark-alm-controller

Length of output: 1894


🏁 Script executed:

#!/bin/bash
# Search for any function that validates or registers Uniswap V4 pools
grep -n 'require.*currency\|require.*token\|pool.*register\|pool.*onboard' src/libraries/UniswapV4Lib.sol -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Check the full implementation of critical validation functions
sed -n '450,548p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 3408


🏁 Script executed:

#!/bin/bash
# Search for any pool whitelist or registration mechanism
rg -n 'whitelist.*pool\|pool.*register\|pool.*onboard' src/ -i -A 2 -B 2

Repository: sparkdotfi/spark-alm-controller

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check if there are any restrictions on what pools can be used
grep -n 'poolId\|PoolKey' src/MainnetController.sol | head -30

Repository: sparkdotfi/spark-alm-controller

Length of output: 752


🏁 Script executed:

#!/bin/bash
# Look for any other setups or constraints in the test files about 1:1 assets
rg -n '1:1|equal.*asset|token.*token|currency.*currency' test/mainnet-fork/Uniswapv4.t.sol | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 1212


🏁 Script executed:

#!/bin/bash
# Let's check if there's any validation when setting tick limits or creating pools
# Look at the full setUniswapV4TickLimits function and surrounding context
sed -n '320,350p' src/MainnetController.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 1147


🏁 Script executed:

#!/bin/bash
# Check if poolId can be set arbitrarily or if there's a whitelist
rg -n 'poolId.*require\|whitelist.*poolId\|validate.*pool' src/ --type=sol -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Look at _getNormalizedBalance and understand the rate limit calculation
sed -n '500,510p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 463


🏁 Script executed:

#!/bin/bash
# Let's search more carefully for any asset validation or 1:1 checks
rg -n 'currency0.*currency1\|token0.*token1' src/libraries/UniswapV4Lib.sol -B 2 -A 2

Repository: sparkdotfi/spark-alm-controller

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check if there's any admin function that validates pools before they're used
rg -n 'onboard\|register\|add.*pool\|setup.*pool' src/ -i -A 3

Repository: sparkdotfi/spark-alm-controller

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Let's look at the full _increaseLiquidity function to understand rate limit calculation
sed -n '298,350p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 2166


🏁 Script executed:

#!/bin/bash
# Let's look at the swap function to see how rate limits work there
sed -n '170,200p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 1081


🏁 Script executed:

#!/bin/bash
# Check if there's any validation when a pool is first configured
# Look for any function that takes poolId and validates assets
grep -n 'function.*poolId\|function.*pool' src/MainnetController.sol | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 268


🏁 Script executed:

#!/bin/bash
# Check if there's any place where pools are registered or validated
sed -n '1,50p' src/MainnetController.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 2408


🏁 Script executed:

#!/bin/bash
# Look at how tests set up pools to see if they validate 1:1 assets
head -150 test/mainnet-fork/Uniswapv4.t.sol | tail -50

Repository: sparkdotfi/spark-alm-controller

Length of output: 2440


🏁 Script executed:

#!/bin/bash
# Check the test setup for pool creation
sed -n '100,150p' test/mainnet-fork/Uniswapv4.t.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 2441


🏁 Script executed:

#!/bin/bash
# Let's check if there are any pool validation functions we might have missed
grep -n 'function.*V4\|function.*Uniswap' src/MainnetController.sol | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 277


🏁 Script executed:

#!/bin/bash
# One final check - look for any require statements that check token decimals or prices
grep -n 'decimals\|price\|exchange.*rate' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 270


Add validation to enforce the 1:1 asset constraint for Uniswap V4 pools.

The 1:1 asset requirement is documented in the README but not enforced in code. The setUniswapV4TickLimits function only validates tick parameters without checking that token0 and token1 are equal-valued assets. While the _increaseLiquidity function includes a comment acknowledging the assumption that tokens are valued equally, no programmatic validation prevents configuring pools with mismatched asset values.

This is critical because the rate limit calculation normalizes both tokens to 18 decimals and sums them without accounting for actual USD values (per the learned design decision about normalized balances). An admin could inadvertently—or maliciously—configure a non-1:1 pool and create a rate limit bypass via price discrepancies.

Consider adding validation in setUniswapV4TickLimits to verify token0 and token1 are equal-valued assets before allowing pool configuration.

🤖 Prompt for AI Agents
In @README.md at line 150, Add programmatic validation in setUniswapV4TickLimits
to enforce the README's 1:1 asset requirement: before accepting tick limits,
verify token0 and token1 represent equal-valued assets (e.g., compare their
canonical asset IDs or use the same oracle/price feed to ensure identical USD
value per normalized unit) and revert with a clear error if they differ; update
any callers/tests accordingly and remove the reliance on the comment in
_increaseLiquidity by making this guarantee upfront in setUniswapV4TickLimits.

Comment on lines +9 to +11
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
"almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709",
"rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838"
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent field ordering.

The field order is almProxy then rateLimits, but the other production configs (unichain, avalanche, optimism) use rateLimits then almProxy. While JSON objects are unordered, maintaining consistency across similar configuration files improves maintainability.

🔎 Suggested reordering for consistency
     "susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
-    "almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709",
-    "rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838"
+    "rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838",
+    "almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
"almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709",
"rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838"
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
"rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838",
"almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709"
🤖 Prompt for AI Agents
In @script/input/1/arbitrum_one-production.json around lines 9 - 11, Reorder the
JSON object fields so they match the ordering used in other production configs:
place "rateLimits" before "almProxy" (keeping "susds" as-is); update the entry
that currently lists "almProxy" then "rateLimits" to instead list "rateLimits"
then "almProxy" for consistency across configs.

Comment on lines +60 to +92
function withdraw(
address proxy,
address aToken,
uint256 amount,
address rateLimits,
bytes32 rateLimitWithdrawId,
bytes32 rateLimitDepositId
) external returns (uint256 amountWithdrawn) {
address pool = IATokenWithPool(aToken).POOL();

// Withdraw underlying from Aave pool, decode resulting amount withdrawn.
// Assumes proxy has adequate aTokens.
amountWithdrawn = abi.decode(
IALMProxy(proxy).doCall(
pool,
abi.encodeCall(
IPool(pool).withdraw,
(IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS(), amount, proxy)
)
),
(uint256)
);

IRateLimits(rateLimits).triggerRateLimitDecrease(
RateLimitHelpers.makeAddressKey(rateLimitWithdrawId, aToken),
amountWithdrawn
);

IRateLimits(rateLimits).triggerRateLimitIncrease(
RateLimitHelpers.makeAddressKey(rateLimitDepositId, aToken),
amountWithdrawn
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add slippage validation for withdrawals.

The withdraw function lacks slippage protection. Unlike deposit (which validates newATokens >= amount * maxSlippage / 1e18), this function does not verify that amountWithdrawn meets minimum expectations. Aave withdrawals can experience rounding or precision loss, potentially returning less than the requested amount.

🔎 Recommended fix: Add slippage validation
 function withdraw(
     address proxy,
     address aToken,
     uint256 amount,
+    uint256 maxSlippage,
     address rateLimits,
     bytes32 rateLimitWithdrawId,
     bytes32 rateLimitDepositId
 ) external returns (uint256 amountWithdrawn) {
+    require(maxSlippage != 0, "MC/max-slippage-not-set");
+
     address pool = IATokenWithPool(aToken).POOL();

     // Withdraw underlying from Aave pool, decode resulting amount withdrawn.
     // Assumes proxy has adequate aTokens.
     amountWithdrawn = abi.decode(
         IALMProxy(proxy).doCall(
             pool,
             abi.encodeCall(
                 IPool(pool).withdraw,
                 (IATokenWithPool(aToken).UNDERLYING_ASSET_ADDRESS(), amount, proxy)
             )
         ),
         (uint256)
     );

+    require(
+        amountWithdrawn >= amount * maxSlippage / 1e18,
+        "MC/slippage-too-high"
+    );
+
     IRateLimits(rateLimits).triggerRateLimitDecrease(
         RateLimitHelpers.makeAddressKey(rateLimitWithdrawId, aToken),
         amountWithdrawn
     );

     IRateLimits(rateLimits).triggerRateLimitIncrease(
         RateLimitHelpers.makeAddressKey(rateLimitDepositId, aToken),
         amountWithdrawn
     );
 }

pragma solidity ^0.8.21;

import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Prefer using the configured remapping for consistency.

The import uses a relative path ../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol instead of the remapping @openzeppelin/contracts/token/ERC20/IERC20.sol configured in foundry.toml (line 17). Consistent use of remappings improves maintainability.

🔎 Proposed fix
-import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
🤖 Prompt for AI Agents
In @src/libraries/CCTPLib.sol at line 4, The import in CCTPLib.sol uses a
relative path to OpenZeppelin instead of the project remapping; update the
import line that references IERC20 to use the remapped path
"@openzeppelin/contracts/token/ERC20/IERC20.sol" (replace the current
"../../lib/openzeppelin-contracts/..." import) so the IERC20 import uses the
configured foundry remapping for consistency and maintainability.

pragma solidity ^0.8.21;

import { IERC20 } from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Prefer using the configured remapping for consistency.

The import uses a relative path ../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol instead of the remapping @openzeppelin/contracts/token/ERC20/IERC20.sol configured in foundry.toml (line 17). While both work, using remappings consistently across the codebase improves maintainability and resilience to directory structure changes.

🔎 Proposed fix
-import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
🤖 Prompt for AI Agents
In @src/libraries/PSMLib.sol at line 4, Replace the relative OpenZeppelin import
with the remapped path: update the import in PSMLib.sol that currently
references ../../lib/openzeppelin-contracts/... to use
@openzeppelin/contracts/token/ERC20/IERC20.sol so the IERC20 symbol is imported
via the configured Foundry remapping for consistency and future-proofing.

Comment on lines +30 to +35
uint256 internal constant _V4_SWAP = 0x10;

// NOTE: From https://docs.uniswap.org/contracts/v4/deployments (Ethereum Mainnet).
address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
address internal constant _POSITION_MANAGER = 0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e;
address internal constant _ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded mainnet addresses limit deployability.

These constants (_PERMIT2, _POSITION_MANAGER, _ROUTER) are Ethereum mainnet-specific. If this library needs to support other networks (testnets, L2s), these addresses will differ.

Consider passing these addresses as parameters or using a registry pattern if multi-chain deployment is anticipated. For now, document that this library is mainnet-only.

🤖 Prompt for AI Agents
In @src/libraries/UniswapV4Lib.sol around lines 30 - 35, The three addresses
_PERMIT2, _POSITION_MANAGER, and _ROUTER are hardcoded to mainnet, which
prevents multi-chain deployment; replace these internal constant addresses with
configurable values (e.g., constructor/initializer-set immutable variables or
function parameters) or route lookups through a registry contract so the library
can be reused across networks, and if you keep them constant add a clear comment
that UniswapV4Lib is mainnet-only. Update all usages of _PERMIT2,
_POSITION_MANAGER, and _ROUTER to read the new configurable variables or
registry lookups.

Comment on lines +253 to +298
function _approveWithPermit2(
address proxy,
address token,
address spender,
uint256 amount
)
internal
{
require(amount <= type(uint160).max, "MC/amount-too-large-for-permit2");

// Approve the Permit2 contract to spend none of the token (success is optional).
// NOTE: We don't care about the success of this call, since the only outcomes are:
// - the allowance is 0 (it was reset or was already 0)
// - the allowance is not 0, in which case the success of the overall set of
// operations is dependent on the success of the subsequent calls.
// In other words, this is a convenience call that may not even be needed for success.
proxy.call(
abi.encodeCall(
IALMProxy.doCall,
(token, abi.encodeCall(IERC20Like.approve, (_PERMIT2, 0)))
)
);

if (amount != 0) {
// Approve the Permit2 contract to spend the amount of token (success is mandatory).
bytes memory approveResult = IALMProxy(proxy).doCall(
token,
abi.encodeCall(IERC20Like.approve, (_PERMIT2, amount))
);

// Revert if approve returns anything, and that anything is not `true`.
require(
approveResult.length == 0 || abi.decode(approveResult, (bool)),
"MC/permit2-approve-failed"
);
}

// Finally, approve the spender to spend the token via Permit2.
IALMProxy(proxy).doCall(
_PERMIT2,
abi.encodeCall(
IPermit2Like.approve,
(token, spender, uint160(amount), uint48(block.timestamp))
)
);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unchecked low-level call result may mask failures.

Line 269-274 uses proxy.call(...) without checking the return value. While the comment explains this is intentional (the call is a "convenience" reset), if the proxy itself reverts for an unexpected reason (e.g., out of gas), this would be silently ignored.

The current approach is acceptable given the documented rationale, but consider logging or emitting an event if the reset fails for debugging purposes in production.

🤖 Prompt for AI Agents
In @src/libraries/UniswapV4Lib.sol around lines 253 - 298, In
_approveWithPermit2, replace the unchecked proxy.call(...) reset with an
explicit (bool success, bytes memory data) = proxy.call(...) and, if success is
false, emit a non-reverting event (e.g., Permit2ResetFailed(address proxy,
address token, bytes data)) so failures (like out-of-gas or unexpected reverts)
are recorded for debugging while preserving the original non-fatal behavior; add
the event declaration and emit it when success==false, but do not change the
current non-revert semantics that the comment intends.

Comment on lines +349 to +382
function _decreaseLiquidity(
address proxy,
address rateLimits,
bytes32 poolId,
address token0,
address token1,
bytes memory callData
)
internal
{
// Get token balances before liquidity decrease.
uint256 startingBalance0 = _getBalance(token0, proxy);
uint256 startingBalance1 = _getBalance(token1, proxy);

// Perform action.
IALMProxy(proxy).doCall(_POSITION_MANAGER, callData);

// Get token balances after liquidity decrease.
uint256 endingBalance0 = _getBalance(token0, proxy);
uint256 endingBalance1 = _getBalance(token1, proxy);

// NOTE: The limitation of this integration is the assumption that the tokens are valued
// equally (i.e. 1.000000 USDC = 1.000000000000000000 USDS).
uint256 rateLimitDecrease =
_getNormalizedBalance(token0, endingBalance0 - startingBalance0) +
_getNormalizedBalance(token1, endingBalance1 - startingBalance1);

// Perform rate limit decrease.
// NOTE: Rate limit decrease includes any token0 or token1 received due to fees.
IRateLimits(rateLimits).triggerRateLimitDecrease(
RateLimitHelpers.makeBytes32Key(LIMIT_WITHDRAW, poolId),
rateLimitDecrease
);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Potential underflow if balances decrease unexpectedly.

Lines 373-374 compute endingBalance0 - startingBalance0 without checking that ending >= starting. If the position manager has a bug or unexpected behavior that reduces balances, this would underflow.

🔎 Suggested defensive check
+        require(endingBalance0 >= startingBalance0, "MC/unexpected-balance0-decrease");
+        require(endingBalance1 >= startingBalance1, "MC/unexpected-balance1-decrease");
+
         uint256 rateLimitDecrease =
             _getNormalizedBalance(token0, endingBalance0 - startingBalance0) +
             _getNormalizedBalance(token1, endingBalance1 - startingBalance1);
🤖 Prompt for AI Agents
In @src/libraries/UniswapV4Lib.sol around lines 349 - 382, In
_decreaseLiquidity, avoid underflow when computing endingBalance0 -
startingBalance0 and endingBalance1 - startingBalance1 by first computing deltas
safely: for each token compute delta as ending >= starting ? ending - starting :
0 (or revert with a clear require if you prefer failing-fast); then pass those
non-negative deltas to _getNormalizedBalance and use the sum for
rateLimitDecrease before calling IRateLimits.triggerRateLimitDecrease on the
RateLimit key. Ensure the fix is applied to the function _decreaseLiquidity and
update the inline comment to reflect the defensive check.

@lucas-manuel lucas-manuel merged commit 4f1f750 into master Jan 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants