Skip to content

Latest commit

 

History

History
270 lines (200 loc) · 9.75 KB

File metadata and controls

270 lines (200 loc) · 9.75 KB

Rate Limiter Implementation - #262

Document status (2026-07-29): This file mixes an original implementation report with claims that are no longer accurate for the current tree. Sections marked ⚠️ STALE describe aspirational or outdated state. See Current implementation snapshot below before relying on setup or integration guidance.

Current implementation snapshot

Item Actual state
Contract Contracts/src/RateLimiter.sol — standalone AccessControl contract with per-limit windows, operator-gated recordOperation, and admin configuration
Forge tests in CI Contracts/test/*.sol run via cd Contracts && forge test (see Contracts/.github/workflows/test.yml)
RateLimiter tests ⚠️ STALE path: test/RateLimiter.t.sol at repo root imports ../src/RateLimiter.sol and is not part of the Contracts/ Foundry project; treat as legacy/orphaned until moved to Contracts/test/
Trading / MarketMaker integration Not wiredTrading.sol and MarketMaker.sol do not call RateLimiter
Backend rate limiting Separate stack: Nest Backend/src/rate-limiter/ and Express Backend/middleware/rateLimiter.js — not the on-chain contract
Production readiness Contract code exists and is unit-testable in isolation; end-to-end enforcement is a Phase 4+ dependency (see Blocked / Phase dependencies)

✅ Implementation Complete (contract module)

The RateLimiter library contract was implemented with configurable limits, exemptions, and status queries. The bullets below remain accurate for the contract in isolation.

Files

1. RateLimiter.sol

  • Location: Contracts/src/RateLimiter.sol
  • Features:
    • Multiple independent rate limits with unique IDs
    • Configurable max operations and time windows
    • Enable/disable limits without reconfiguration
    • User-level exemptions for privileged users
    • Automatic window reset after timeout
    • Separate tracking per user per limit
    • Detailed status and metrics queries

2. RateLimiter.t.sol — ⚠️ STALE location / CI path

  • Documented location: test/RateLimiter.t.sol (repository root)
  • CI / Foundry project: tests under Contracts/test/ are what forge test runs from Contracts/
  • Action for contributors: move or recreate coverage at Contracts/test/RateLimiter.t.sol before treating test counts below as CI-gated
  • Historical claim: 35+ test cases — applies to the root test/RateLimiter.t.sol file if compiled in a compatible Foundry layout (not verified in Contracts/ CI today)

Acceptance criteria (contract-only)

The following held for the original contract delivery. On-chain enforcement in Trading/MarketMaker is still outstanding.

1. Limit Operation Frequency ✅ (contract API)

  • configureRateLimit(limitId, maxOps, timeWindow, enabled) - Set limits
  • Automatic window reset after timeout
  • Operations blocked when limit exceeded (via recordOperation)

2. Track Operation Counts ✅

  • getOperationCount(limitId, user) - Current count in active window
  • getOperationStatus(limitId, user) - Full status object
  • Per-user, per-limit tracking
  • Automatic reset on window expiration

3. Enforce Rate Limits ✅ (when called)

  • checkRateLimit(limitId, user) - Returns boolean (allowed/blocked)
  • recordOperation(limitId, user) - Reverts if limit exceeded (OPERATOR_ROLE only)
  • recordOperationIfAllowed(limitId, user) - Returns boolean (OPERATOR_ROLE only)

4. Support Different Limits ✅

  • Multiple independent limits per system (e.g. LIMIT_TRADES, LIMIT_WITHDRAWALS)
  • Each limit has own max operations and time window

5. Provide Limit Queries ✅

  • getOperationCount(), getOperationStatus(), getTimeToNextWindow(), isRateLimited(), limitsExist(), getRateLimitConfig()

Core API

Configuration (Admin Only — ADMIN_ROLE / DEFAULT_ADMIN_ROLE)

configureRateLimit(bytes32 limitId, uint256 maxOps, uint256 timeWindow, bool enabled)
enableRateLimit(bytes32 limitId)
disableRateLimit(bytes32 limitId)

Rate Limiting (Operator — OPERATOR_ROLE)

checkRateLimit(bytes32 limitId, address user) → bool allowed
recordOperation(bytes32 limitId, address user) // reverts if limited
recordOperationIfAllowed(bytes32 limitId, address user) → bool allowed

Permission Control (Admin Only)

setLimitOverride(bytes32 limitId, address user, bool overridden)
isUserExempt(bytes32 limitId, address user) → bool

Status Queries (Anyone — public view functions)

getRateLimitConfig(bytes32 limitId) → (maxOps, timeWindow, enabled)
getOperationCount(bytes32 limitId, address user) → uint256 count
getOperationStatus(bytes32 limitId, address user) → (count, max, remaining, timeUntilReset, isLimited)
getTimeToNextWindow(bytes32 limitId, address user) → uint256 secondsUntilReset
isRateLimited(bytes32 limitId, address user) → bool
limitsExist(bytes32 limitId) → bool

Management (Admin Only)

resetUserLimits(bytes32 limitId, address user)

Key Features

(Documentation below describes contract behaviour — still accurate.)

Multiple Independent Limits

rateLimiter.configureRateLimit(LIMIT_TRADES, 100, 1 hours, true);
rateLimiter.configureRateLimit(LIMIT_WITHDRAWALS, 10, 24 hours, true);

Per-User Tracking

Each user has independent counters for each limit ID.

Automatic Window Reset

Windows expire after configured timeWindow; counters reset on the next qualifying operation.

User Exemptions

rateLimiter.setLimitOverride(LIMIT_TRADES, admin, true);

Enable/Disable Without Reconfiguration

rateLimiter.disableRateLimit(LIMIT_TRADES);
rateLimiter.enableRateLimit(LIMIT_TRADES);

Events

event RateLimitConfigured(bytes32 indexed limitId, uint256 maxOperations, uint256 timeWindow, bool enabled)
event OperationAllowed(bytes32 indexed limitId, address indexed user, uint256 operationCount)
event OperationBlocked(bytes32 indexed limitId, address indexed user, string reason)
event RateLimitReset(bytes32 indexed limitId, address indexed user)
event LimitOverrideSet(bytes32 indexed limitId, address indexed user, bool overridden)
event WindowRolled(bytes32 indexed limitId, address indexed user, uint256 newWindowStart)

Test coverage — ⚠️ STALE CI claims

Category Historical claim Current note
Configuration 6 tests In root test/RateLimiter.t.sol only
Rate Limiting 6 tests Not run by Contracts/ CI until relocated
Overrides 3 tests
Status Queries 8 tests
Management 2 tests
Edge Cases 5 tests
TOTAL 35 ⚠️ Not gating Contracts CI today

Architecture

Data Structures

RateLimitConfig - Per-limit configuration:

struct RateLimitConfig {
    uint256 maxOperations;
    uint256 timeWindow;
    bool enabled;
}

OperationTracker - Per-user-per-limit tracking:

struct OperationTracker {
    uint256 operationCount;
    uint256 windowStartTime;
    uint256 lastOperationTime;
}

Storage Model

rateLimitConfigs[limitId] → Configuration for limit
operationTrackers[limitId][user] → User's tracker for limit
userLimitOverrides[user][limitId] → Override flag

Usage examples (deploy + configure)

RateLimiter limiter = new RateLimiter();

limiter.configureRateLimit(
    keccak256("TRADES"),
    100,
    1 hours,
    true
);

Security features (contract)

Role-Based Access Control — OpenZeppelin AccessControl (ADMIN_ROLE, OPERATOR_ROLE)

Input Validation — positive maxOperations / timeWindow, non-zero limitId

Safe Math — Solidity 0.8.20+ overflow checks

No Reentrancy Risk — no external calls in limit updates

⚠️ STALE claim removed: “Users: Can only query their own status” — getOperationStatus and related getters are public and readable for any (limitId, user) pair (standard for on-chain counters).


Integration recommendations — ⚠️ STALE / not implemented

The snippets below are design targets, not current behaviour. Trading.sol, MarketMaker.sol, and withdrawal flows do not invoke RateLimiter yet.

Market Operations (planned)

// ⚠️ NOT WIRED — Phase 4 target (e.g. P4 on-chain RateLimiter in Trading)
function executeTrade(bytes calldata tradeData) external {
    rateLimiter.recordOperation(LIMIT_TRADES, msg.sender);
    // Execute trade...
}

Monitoring Dashboards (planned)

Off-chain indexers may call getOperationStatus today; product UI wiring is Phase 4+.


Blocked / Phase dependencies

Capability Blocker Phase
On-chain trade rate limits in Trading / MarketMaker Contract not composed into trading path Phase 4 (hardening)
CI-gated RateLimiter forge tests Tests live under repo-root test/, outside Contracts/ Foundry project Phase 1 / 4 — relocate to Contracts/test/
Unified backend + on-chain limit story Nest rate-limiter module ≠ Solidity RateLimiter Phase 4
“Production ready” end-to-end Requires deployment, role holders, and caller integration Phase 4+

Local build / run

Green path for collaborators (see CONTRIBUTING.md):

cd Contracts && forge build && forge test
cd Backend && npm install && npm run start:dev
cd Frontend && npm install && npm run dev

No changes to contract logic are required for this documentation update.


Status: Contract implemented ✅ — product integration and CI test path ⚠️ incomplete (Phase 4+)