Skip to content

Instantly share code, notes, and snippets.

@sujithsomraaj
Last active March 27, 2025 16:43
Show Gist options
  • Select an option

  • Save sujithsomraaj/dd246f9e8701a2735887aed569f9b43d to your computer and use it in GitHub Desktop.

Select an option

Save sujithsomraaj/dd246f9e8701a2735887aed569f9b43d to your computer and use it in GitHub Desktop.

Rate Limiting Bypass in RateLimitedIsm and RateLimitedHook

Context

RateLimited.sol#L118 RateLimitedIsm.sol#L13 RateLimitedHook.sol#L32

Summary

A vulnerability exists in the contract that inherit the RateLimited contract, allowing an attacker to bypass intended rate-limiting access-control mechanisms.

Because the validateAndConsumeFilledLevel() function is publicly callable, anyone can exhaust the rate limit, causing a denial of service for legitimate transactions. This affects both the RateLimitedHook and RateLimitedIsm contracts that inherit from RateLimited.

Description

The RateLimited contract implements a token bucket algorithm for rate limiting. However, it exposes the core rate-limiting function validateAndConsumeFilledLevel() as a public function without access control:

function validateAndConsumeFilledLevel(
    uint256 _consumedAmount
) public returns (uint256) {
    uint256 adjustedFilledLevel = calculateCurrentLevel();
    require(_consumedAmount <= adjustedFilledLevel, "RateLimitExceeded");

    // Reduce the filledLevel and update lastUpdated
    uint256 _filledLevel = adjustedFilledLevel - _consumedAmount;
    filledLevel = _filledLevel;
    lastUpdated = block.timestamp;

    emit ConsumedFilledLevel(filledLevel, lastUpdated);

    return _filledLevel;
}

This allows any external actor to call this function directly and consume the entire available capacity (i.e., filledLevel), effectively performing a denial-of-service attack on both:

  • RateLimitedHook - preventing legitimate token transfers from being dispatched
  • RateLimitedIsm - preventing legitimate message verification

PoC

The vulnerability is demonstrated in the provided test cases:

function testRateLimitedHook_allowsTransfer_ifUnderLimit(
    uint128 _amount,
    uint128 _time
) external {
    // Warp to a random time, get it's filled level, and try to transfer less than the target max
    vm.warp(_time);
    uint256 filledLevelBefore = rateLimitedHook.calculateCurrentLevel();
    rateLimitedHook.validateAndConsumeFilledLevel(filledLevelBefore);
    
    uint256 limitAfter = rateLimitedHook.calculateCurrentLevel();
    assertEq(limitAfter, 0);
}

function testRateLimitedIsm_verify(uint128 _amount) external {
    vm.assume(_amount <= rateLimitedIsm.calculateCurrentLevel());

    rateLimitedIsm.validateAndConsumeFilledLevel(rateLimitedIsm.calculateCurrentLevel());
    vm.prank(address(localMailbox));
    localMailbox.process(bytes(""), _encodeTestMessage(_amount));
}

In both cases, an attacker can call validateAndConsumeFilledLevel() with the current available capacity (obtained via calculateCurrentLevel()), exhausting the rate limit completely. After this attack, legitimate transfers will be blocked until the rate limit refills according to the defined refillRate.

Impact

Severity: High

Likelihood: High

Impact: High

This vulnerability enables an attacker to:

  • Block all cross-chain token transfers for some time (up to DURATION, which is 1 day)
  • Prevent message verification in the ISM contract
  • Force users to wait for rate limit refill or pay higher gas prices to compete with the attacker
  • Disrupt the regular operation of the protocol with minimal cost
  • The attack can be performed repeatedly, creating a persistent denial of service condition for the affected contracts.

Recommendation

Implement proper access control on the validateAndConsumeFilledLevel() function to restrict its usage to authorized callers only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment