Rate Limiter
The RateLimiter.sol
is used to control the number of cross-chain messages that can be sent within a certain time window, ensuring that the OApp is not spammed by too many transactions at once. It's particularly useful for:
Preventing Denial of Service Attacks: By setting thresholds on the number of messages that can be processed within a given timeframe, the
RateLimiter
acts as a safeguard against DoS attacks, where malicious actors might attempt to overload an OApp with a flood of transactions. This protection ensures that the OApp remains accessible and functional for legitimate users, even under attempted attacks.Regulatory Compliance: Some applications may need to enforce limits to comply with legal or regulatory requirements.
The RateLimiter
is only useful under specific application use cases. It will not be necessary for most OApps and can even be counterproductive for more generic applications:
Low Traffic Applications: If your application doesn't expect high volumes of traffic, implementing a rate limiter might be unnecessary overhead.
Critical Systems Requiring Immediate Transactions: For systems where transactions need to be processed immediately without delay, rate limiting could hinder performance.
Installation
To begin working with LayerZero contracts, you can install the OApp npm package to an existing project:
npm install @layerzerolabs/lz-evm-oapp-v2
Usage
Import the RateLimiter.sol
contract into your OApp contract file and inherit the contract:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import { OApp } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
import { RateLimiter } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/utils/RateLimiter.sol";
contract MyOApp is OApp, RateLimiter {
// ...contract
}
Initializing Rate Limits
In the constructor of your contract, initialize the rate limits using _setRateLimits
with an array of RateLimitConfig
structs.
Example:
constructor(
RateLimitConfig[] memory _rateLimitConfigs,
address _lzEndpoint,
address _delegate
) OApp(_lzEndpoint, _delegate) {
_setRateLimits(_rateLimitConfigs);
}
// ...Rest of contract code
RateLimitConfig
Struct:
struct RateLimitConfig {
uint32 dstEid; // destination endpoint ID
uint256 limit; // arbitrary limit of messages/tokens to transfer
uint256 window; // window of time before limit resets
}
Setting Rate Limits
Provide functions to set or update rate limits dynamically. This can include a function to adjust individual or multiple rate limits and a mechanism to authorize who can make these changes (typically restricted to the contract owner or a specific role).
/**
* @dev Sets the rate limits based on RateLimitConfig array. Only callable by the owner or the rate limiter.
* @param _rateLimitConfigs An array of RateLimitConfig structures defining the rate limits.
*/
function setRateLimits(
RateLimitConfig[] calldata _rateLimitConfigs
) external {
if (msg.sender != rateLimiter && msg.sender != owner()) revert OnlyRateLimiter();
_setRateLimits(_rateLimitConfigs);
}
Checking Rate Limits During Send Calls
Before processing transactions, use _checkAndUpdateRateLimit
to ensure the transaction doesn't exceed the set limits. This function should be called in any transactional functions, such as message passing or token transfers.
Message Passing
function send(
uint32 _dstEid,
string memory _message,
bytes calldata _options
) external payable {
_checkAndUpdateRateLimit(_dstEid, 1); // updating the rate limit per message sent
bytes memory _payload = abi.encode(_message); // Encodes message as bytes.
_lzSend(
_dstEid, // Destination chain's endpoint ID.
_payload, // Encoded message payload being sent.
_options, // Message execution options (e.g., gas to use on destination).
MessagingFee(msg.value, 0), // Fee struct containing native gas and ZRO token.
payable(msg.sender) // The refund address in case the send call reverts.
);
}
Token Transfers
/**
* @dev Checks and updates the rate limit before initiating a token transfer.
* @param _amountLD The amount of tokens to be transferred.
* @param _minAmountLD The minimum amount of tokens expected to be received.
* @param _dstEid The destination endpoint identifier.
* @return amountSentLD The actual amount of tokens sent.
* @return amountReceivedLD The actual amount of tokens received.
*/
function _debit(
uint256 _amountLD,
uint256 _minAmountLD,
uint32 _dstEid
) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
_checkAndUpdateRateLimit(_dstEid, _amountLD);
return super._debit(_amountLD, _minAmountLD, _dstEid);
}