Skip to content

feat: add OFT rate limiting support#135

Open
dhruvinparikh wants to merge 1 commit into
FraxFinance:masterfrom
dhruvinparikh:feature/oft-rate-limits
Open

feat: add OFT rate limiting support#135
dhruvinparikh wants to merge 1 commit into
FraxFinance:masterfrom
dhruvinparikh:feature/oft-rate-limits

Conversation

@dhruvinparikh
Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings April 24, 2026 04:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a reusable on-chain rate limiting module and wires it into the project’s OFT/OFTAdapter contracts, along with deployment/ops support and Foundry tests to validate expected capacity/refill behavior.

Changes:

  • Added RateLimiterModule (config/state storage + consume/checkpoint + availability getters).
  • Integrated rate limiting into OFT/OFTAdapter implementations by overriding quoteOFT, _debit, and _credit.
  • Added deployment helper script for applying rate limit configs and extended the ops review script to fetch rate limit config/state.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
contracts/modules/RateLimiterModule.sol New core module implementing configurable inbound/outbound linear refill rate limiting.
contracts/FraxOFTUpgradeable.sol Adds rate limiting consumption in _debit/_credit and updates quoteOFT to report rate-limited max.
contracts/FraxOFTAdapterUpgradeable.sol Adds rate limiting on adapter transfers and rate-limited quoteOFT.
contracts/FraxOFTMintableAdapterUpgradeable.sol Applies rate limiting to mint/burn adapter flows and rate-limited quoteOFT.
contracts/FraxOFTMintableAdapterUpgradeableTIP20.sol Applies rate limiting to TIP20 adapter flows and rate-limited quoteOFT.
contracts/frxUsd/FrxUSDOFTUpgradeable.sol Adds rate limiting to frxUSD OFT send/receive via _debit/_credit + quoteOFT.
contracts/frxUsd/SFrxUSDOFTUpgradeable.sol Adds rate limiting to sfrxUSD OFT send/receive via _debit/_credit + quoteOFT.
contracts/WFRAXTokenOFTUpgradeable.sol Adds rate limiting to WFRAX OFT send/receive via _debit/_credit + quoteOFT.
test/foundry/contracts/RateLimiterModuleTest.t.sol New Foundry tests covering outbound/inbound limits, linear refill, quote capacity, and adapter behavior.
scripts/DeployFraxOFTProtocol/inherited/SetRateLimits.s.sol New deployment helper to set global/default/per-eid rate limit configs and serialize txs.
scripts/DeployFraxOFTProtocol/DeployFraxOFTProtocol.s.sol Integrates setRateLimits() into the deployment flow.
scripts/ops/review-config/get-config.ts Extends config review output with rate limit global/default/per-eid config/state + availability.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

sharedGlobalConfig = {
isGloballyDisabled: oftres.isGloballyDisabled,
};
} catch {}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Empty catch {} blocks will silently swallow RPC/ABI issues and produce partial configs without any signal, which is hard to debug operationally. Consider at least capturing the error (or toggling verbose logging) so failures are observable.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +61
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitGlobalConfig memory config) {}

function desiredDefaultRateLimitConfig(
address /* _connectedOft */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}

function desiredRateLimitConfig(
address /* _connectedOft */,
uint32 /* _eid */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

These desired* hook functions have empty bodies but non-void return types; they’ll compile but implicitly return default values, which is easy to misread as an accidental omission. Consider explicitly returning (false, config) (or marking them virtual with no body in an abstract contract) to make the default behavior unambiguous.

Suggested change
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitGlobalConfig memory config) {}
function desiredDefaultRateLimitConfig(
address /* _connectedOft */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}
function desiredRateLimitConfig(
address /* _connectedOft */,
uint32 /* _eid */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitGlobalConfig memory config) {
return (false, config);
}
function desiredDefaultRateLimitConfig(
address /* _connectedOft */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {
return (false, config);
}
function desiredRateLimitConfig(
address /* _connectedOft */,
uint32 /* _eid */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {
return (false, config);
}

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +61
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitGlobalConfig memory config) {}

function desiredDefaultRateLimitConfig(
address /* _connectedOft */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}

function desiredRateLimitConfig(
address /* _connectedOft */,
uint32 /* _eid */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

Same as above: desiredRateLimitConfig has an empty body and implicitly returns default values. An explicit return (or abstract declaration) would make it clearer that downstream scripts must override this hook to opt in.

Suggested change
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitGlobalConfig memory config) {}
function desiredDefaultRateLimitConfig(
address /* _connectedOft */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}
function desiredRateLimitConfig(
address /* _connectedOft */,
uint32 /* _eid */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {}
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitGlobalConfig memory config) {
return (false, IRateLimitedOFT.RateLimitGlobalConfig({ isGloballyDisabled: false }));
}
function desiredDefaultRateLimitConfig(
address /* _connectedOft */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {
return (
false,
IRateLimitedOFT.RateLimitConfig({
overrideDefaultConfig: false,
outboundEnabled: false,
inboundEnabled: false,
outboundLimit: 0,
inboundLimit: 0,
outboundWindow: 0,
inboundWindow: 0
})
);
}
function desiredRateLimitConfig(
address /* _connectedOft */,
uint32 /* _eid */
) public view virtual returns (bool shouldSet, IRateLimitedOFT.RateLimitConfig memory config) {
return (
false,
IRateLimitedOFT.RateLimitConfig({
overrideDefaultConfig: false,
outboundEnabled: false,
inboundEnabled: false,
outboundLimit: 0,
inboundLimit: 0,
outboundWindow: 0,
inboundWindow: 0
})
);
}

Copilot uses AI. Check for mistakes.
Comment on lines +342 to +347
if (_elapsed >= _window) {
return 0;
}

uint256 replenished = (_limit * _elapsed) / _window;
return replenished >= _usage ? 0 : _usage - replenished;
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

_decayUsage computes replenished as (_limit * _elapsed) / _window, which can overflow and revert for large _limit values (owner misconfiguration), effectively bricking reads/consumption for that eid. Consider using a mulDiv-style computation to avoid overflow (e.g., full-precision mulDiv) and/or bounding _limit during config validation.

Copilot uses AI. Check for mistakes.
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.

2 participants