Skip to content

Conversation

@deluca-mike
Copy link
Collaborator

@deluca-mike deluca-mike commented Nov 10, 2025

Summary by CodeRabbit

Release Notes

  • Refactor
    • Consolidated deposit, withdrawal, and redemption operations into a dedicated library for improved consistency and maintainability.
    • Enhanced exchange rate calculations and rate-limiting mechanisms for more reliable transaction processing.

@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

Walkthrough

The PR introduces a new ERC4626Lib library that encapsulates ERC4626 token operations (deposit, withdraw, redeem) with integrated rate-limit management. MainnetController is refactored to delegate these operations to the library, converting EXCHANGE_RATE_PRECISION from a constant to a public accessor. AaveLib receives minor formatting adjustments.

Changes

Cohort / File(s) Summary
ERC4626 Library Introduction
src/libraries/ERC4626Lib.sol
New library implementing deposit, withdraw, and redeem functions with ERC4626 compliance, exchange-rate validation, and rate-limit orchestration via IALMProxy and RateLimitHelpers. Includes getExchangeRate helper and EXCHANGE_RATE_PRECISION constant.
MainnetController Refactoring
src/MainnetController.sol
Refactored deposit/withdraw/redeem ERC4626 flows to delegate to ERC4626Lib. Removed internal _getExchangeRate helper. Converted EXCHANGE_RATE_PRECISION state constant to public pure accessor function. Updated setMaxExchangeRate to use ERC4626Lib.getExchangeRate.
AaveLib Formatting
src/libraries/AaveLib.sol
Minor spacing adjustment in IALMProxy import statement; no functional changes.

Sequence Diagrams

sequenceDiagram
    actor User
    participant MC as MainnetController
    participant E4626L as ERC4626Lib
    participant Proxy as IALMProxy
    participant Token as ERC4626Token
    participant RL as RateLimits

    rect rgb(220, 240, 255)
    note over User,RL: deposit() flow
    User->>MC: depositERC4626(token, amount)
    MC->>E4626L: deposit(proxy, token, amount, maxExchangeRate, rateLimits, rateLimitId)
    E4626L->>E4626L: ApproveLib.approve(asset → proxy)
    E4626L->>Proxy: depositERC4626(token, amount)
    Proxy->>Token: transferFrom + deposit
    Token-->>Proxy: shares
    E4626L->>E4626L: getExchangeRate(shares, assets)
    E4626L->>E4626L: assert(exchangeRate <= maxExchangeRate)
    E4626L->>RL: decreaseRateLimit(rateLimitId, amount)
    E4626L-->>MC: shares
    MC-->>User: shares
    end

    rect rgb(220, 255, 220)
    note over User,RL: withdraw() flow
    User->>MC: withdrawERC4626(token, amount)
    MC->>E4626L: withdraw(proxy, token, amount, rateLimits, withdrawId, depositId)
    E4626L->>RL: decreaseRateLimit(withdrawId, amount)
    E4626L->>Proxy: withdrawERC4626(token, amount)
    Proxy->>Token: withdraw
    Token-->>Proxy: shares
    E4626L->>RL: increaseRateLimit(depositId, assets)
    E4626L-->>MC: shares
    MC-->>User: shares
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~40 minutes

  • ERC4626Lib logic: Verify deposit/withdraw/redeem sequences, exchange-rate validation, and rate-limit integration correctness
  • Proxy interaction patterns: Ensure IALMProxy calls are correctly structured with appropriate parameters
  • Rate-limit orchestration: Confirm composite rate-limit keys (withdrawRateLimitId/depositRateLimitId) are properly sequenced in withdraw/redeem flows
  • Exchange-rate computation edge cases: Validate getExchangeRate handles division-by-zero and zero-asset scenarios
  • MainnetController delegation: Confirm all refactored functions properly pass parameters and return values from ERC4626Lib

Suggested reviewers

  • supercontracts
  • lucas-manuel

Poem

🐰✨ A library hops into place, with shares and rates aligned,
Exchange rates checked, rate limits intertwined,
The Controller delegates with grace and ease,
ERC4626 flows now work to please!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title follows the required format with 'feat:' prefix and clearly describes the main change - introduction of an ERC4626 library.
Linked Issues check ✅ Passed The pull request successfully implements the ERC4626 library as requested in SC-1233, introducing library functions for deposit, withdraw, redeem operations and exchange rate calculation.
Out of Scope Changes check ✅ Passed All changes are directly related to creating the ERC4626 library and refactoring MainnetController to use it, with only minor formatting in AaveLib that maintains backward compatibility.
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 sc-1233-erc4626-lib

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

Base automatically changed from sc-1182-aave-lib to dev November 10, 2025 16:55
@github-actions
Copy link

Coverage after merging sc-1233-erc4626-lib into dev will be

99.55%

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.sol98.24%91.67%100%99.16%312–313, 613
   MainnetController.sol99.11%100%98.11%99.18%500–501
   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%

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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f93c32 and 0b598fe.

📒 Files selected for processing (3)
  • src/MainnetController.sol (3 hunks)
  • src/libraries/AaveLib.sol (1 hunks)
  • src/libraries/ERC4626Lib.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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:

  • 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: test
  • GitHub Check: coverage
  • GitHub Check: build
🔇 Additional comments (9)
src/libraries/AaveLib.sol (1)

9-9: LGTM!

The formatting adjustment to the import statement is purely cosmetic with no functional impact.

src/libraries/ERC4626Lib.sol (3)

45-72: LGTM!

The withdraw function correctly:

  • Triggers withdraw rate limit decrease before the operation
  • Performs the withdrawal via proxy
  • Increases the deposit rate limit after withdrawal (allows re-depositing)
  • Returns the shares burned

The logic and rate limit ordering are correct for the withdraw pattern where the assets amount is specified upfront.


74-101: LGTM!

The redeem function correctly performs the redemption before rate limiting. This ordering is necessary because:

  • redeem specifies shares (input), not assets (output)
  • The assets amount is only known after the redemption completes
  • Rate limits must use the actual assets received

This differs from withdraw (where assets are known upfront) but is the correct pattern for redeem.


103-111: LGTM!

The getExchangeRate function correctly handles all edge cases:

  • Returns 0 for zero assets (handles both 0/0 and 0/shares cases)
  • Reverts for zero shares with non-zero assets (infinite exchange rate is invalid)
  • Calculates the exchange rate with proper precision scaling

The logic is sound and safe.

src/MainnetController.sol (5)

25-25: LGTM!

The import of ERC4626Lib is correctly added to support the refactored ERC4626 operations.


297-308: LGTM!

The refactoring to use ERC4626Lib.getExchangeRate for calculating the max exchange rate is correct and improves code reusability. This eliminates duplication of the exchange rate calculation logic.

Note: This depends on fixing the visibility of EXCHANGE_RATE_PRECISION in ERC4626Lib (see comment on ERC4626Lib.sol line 15).


455-468: LGTM!

The refactoring to delegate ERC4626 deposits to the library is clean and correct. All necessary parameters (proxy, token, amount, maxExchangeRate, rateLimits, rateLimitId) are properly passed through.


470-483: LGTM!

The refactoring to delegate ERC4626 withdrawals to the library is correct. Both rate limit IDs (withdraw and deposit) are properly passed to handle the bidirectional rate limit updates.


485-498: LGTM!

The refactoring to delegate ERC4626 redemptions to the library is correct. The function properly passes the shares and both rate limit IDs, returning the assets received from the redemption.


library ERC4626Lib {

uint256 internal constant EXCHANGE_RATE_PRECISION = 1e36;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Change visibility to public for external access.

The constant is declared as internal but MainnetController.sol line 501 attempts to access it as ERC4626Lib.EXCHANGE_RATE_PRECISION. Internal constants in libraries cannot be accessed from outside the library, causing a compilation error.

Apply this diff to fix the visibility:

-    uint256 internal constant EXCHANGE_RATE_PRECISION = 1e36;
+    uint256 public constant EXCHANGE_RATE_PRECISION = 1e36;
📝 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
uint256 internal constant EXCHANGE_RATE_PRECISION = 1e36;
uint256 public constant EXCHANGE_RATE_PRECISION = 1e36;
🤖 Prompt for AI Agents
In src/libraries/ERC4626Lib.sol around line 15, the constant
EXCHANGE_RATE_PRECISION is declared internal but is referenced externally as
ERC4626Lib.EXCHANGE_RATE_PRECISION; change its visibility to public so it can be
accessed from other contracts/libraries by replacing the visibility specifier
with public (i.e., make the constant public) and recompile to ensure no other
visibility issues remain.

Comment on lines +17 to +43
function deposit(
address proxy,
address token,
uint256 amount,
uint256 maxExchangeRate,
address rateLimits,
bytes32 rateLimitId
) external returns (uint256 shares) {
IRateLimits(rateLimits).triggerRateLimitDecrease(
RateLimitHelpers.makeAddressKey(rateLimitId, token),
amount
);

// Approve asset to token from the proxy (assumes the proxy has enough of the asset).
ApproveLib.approve(IERC4626(token).asset(), proxy, token, amount);

// Deposit asset into the token, proxy receives token shares, decode the resulting shares.
shares = abi.decode(
IALMProxy(proxy).doCall(
token,
abi.encodeCall(IERC4626(token).deposit, (amount, proxy))
),
(uint256)
);

require(getExchangeRate(shares, amount) <= maxExchangeRate, "MC/exchange-rate-too-high");
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM with minor note.

The deposit function correctly:

  • Triggers rate limit decrease before the deposit operation (security best practice)
  • Handles approvals via ApproveLib
  • Validates exchange rate after receiving shares
  • Returns the shares received

Minor note: The error message uses "MC/" prefix which is MainnetController-specific. Consider using "ERC4626/" or a generic prefix for better library reusability.

🤖 Prompt for AI Agents
In src/libraries/ERC4626Lib.sol around lines 17 to 43, the require revert
message uses the MainnetController-specific prefix "MC/" which reduces
reusability of this library; change the revert string to a library-appropriate
prefix such as "ERC4626/exchange-rate-too-high" (or a generic
"LIB/exchange-rate-too-high") so the error clearly identifies this library,
updating the require(...) call accordingly.

Comment on lines +500 to 502
function EXCHANGE_RATE_PRECISION() external pure returns (uint256) {
return ERC4626Lib.EXCHANGE_RATE_PRECISION;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Compilation error due to accessing internal library constant.

This function attempts to access ERC4626Lib.EXCHANGE_RATE_PRECISION, which is declared as internal in the library (ERC4626Lib.sol line 15). Internal constants in libraries cannot be accessed from external contracts, causing a compilation failure.

Resolution: Change the visibility of EXCHANGE_RATE_PRECISION to public in ERC4626Lib.sol (see comment on that file).

Compatibility note: Converting the previous public constant to an external pure function maintains backward compatibility for external callers, as both are accessed via function call syntax.

🤖 Prompt for AI Agents
In src/MainnetController.sol around lines 500-502, the function is failing
compilation because it accesses ERC4626Lib.EXCHANGE_RATE_PRECISION which is
declared internal in ERC4626Lib (line ~15); open ERC4626Lib.sol and change the
EXCHANGE_RATE_PRECISION declaration to be public (or replace the internal
constant with an external pure function that returns the same uint256 value) so
external contracts can reference it; after changing visibility, recompile and
keep the external accessor in MainnetController as-is to preserve backward
compatibility.

@lucas-manuel lucas-manuel merged commit 241b131 into dev Nov 10, 2025
4 checks passed
@lucas-manuel lucas-manuel deleted the sc-1233-erc4626-lib branch November 10, 2025 18:12
lucas-manuel pushed a commit that referenced this pull request Nov 18, 2025
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.

3 participants