Test_RateLimiter

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "contracts/interfaces/IRateLimiter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

/**
 * @notice Rate Limit struct.
 * @param amount The current amount tracked against the rate limit.
 * @param lastUpdated Timestamp representing the last time the rate limit was checked or updated.
 * @param limit This represents the maximum allowed amount within a given window.
 * @param window Defines the duration of the rate limiting window.
 */
struct RateLimit {
    uint256 amount;
    uint256 lastUpdated;
    uint256 limit;
    uint256 window;
}

/**
 * @notice Rate Limit Configuration struct.
 * @param remoteEid The destination endpoint id.
 * @param tokenId The identifier for the token.
 * @param outboundLimit This represents the maximum allowed amount within a given window for outflows.
 * @param outboundWindow Defines the duration of the rate limiting window for outflows.
 * @param inboundLimit This represents the maximum allowed amount within a given window for inflows.
 * @param inboundWindow Defines the duration of the rate limiting window for inflows.
 */
struct RateLimitConfig {
    uint32 remoteEid;
    bytes32 tokenId;
    uint256 outboundLimit;
    uint256 outboundWindow;
    uint256 inboundLimit;
    uint256 inboundWindow;
}

/**
 * @title IRateLimiter
 * @notice Interface for the Rate Limiter, which manages rate limits for outflows and inflows of tokens.
 * @dev Indexes the rate limits by Id, can be done with token addresses etc.
 */
interface IRateLimiter {
    // @dev Custom error messages
    error MessengerIdempotent();
    error OnlyMessenger(address caller);
    error RateLimitExceeded();

    // @dev Events
    event MessengerSet(address indexed messenger);
    event RateLimitsSet(RateLimitConfig[] rateLimitConfig);

    // @dev The address of the Messenger contract, which is used to verify the caller in outflow and inflow functions.
    function messenger() external view returns (address);

    /**
     * @notice Sets the Messenger address, which is used to verify the caller in outflow and inflow functions.
     * @param _messenger The address of the Messenger contract.
     */
    function setMessenger(address _messenger) external;

    /**
     * @notice Configures the rate limits for the specified tokenId and destination endpoint id.
     * @param configs An array of `RateLimitConfig` structs representing the rate limit configurations.
     * - `remoteEid`: The destination endpoint id.
     * - `tokenId`: The identifier for the token.
     * - `outboundLimit`: This represents the maximum allowed amount within a given window for outflows.
     * - `outboundWindow`: Defines the duration of the rate limiting window for outflows.
     * - `inboundLimit`: This represents the maximum allowed amount within a given window for inflows.
     * - `inboundWindow`: Defines the duration of the rate limiting window for inflows.
     */
    function configureRateLimits(RateLimitConfig[] calldata configs) external;

    /**
     * @notice Current amount that can be sent to this dst endpoint id for the given rate limit window and tokenId.
     * @param tokenId The identifier for which the rate limit is being checked.
     * @param tokenAddress The address of the token from the corresponding Id.
     * @param remoteEid The remote endpoint id.
     * @return sendable The current amount that can be sent.
     * @return currentOutbound The current amount used for outbound flows.
     * @return receivable The amount that can be received.
     * @return currentInbound The current amount used for inbound flows.
     */
    function getAmountsAvailable(
        bytes32 tokenId,
        address tokenAddress,
        uint32 remoteEid
    ) external view returns (uint256 sendable, uint256 currentOutbound, uint256 receivable, uint256 currentInbound);

    /**
     * @notice Verifies whether the specified amount falls within the rate limit constraints for the targeted
     * endpoint ID. On successful verification, it updates amountInFlight and lastUpdated. If the amount exceeds
     * the rate limit, the operation reverts.
     * @param tokenId The identifier for the token.
     * @param tokenAddress The address of the token from the corresponding Id.
     * @param dstEid The destination endpoint id.
     * @param amount The amount to outflow.
     */
    function outflow(bytes32 tokenId, address tokenAddress, uint32 dstEid, uint256 amount) external;

    /**
     * @notice To be used when you want to calculate your rate limits as a function of net outbound AND inbound.
     * ie. If you move 150 out, and 100 in, your effective inflight should be 50.
     * Does not need to update decay values, as the inflow is effective immediately.
     * @param tokenId The identifier for the token.
     * @param tokenAddress The address of the token from the corresponding Id.
     * @param srcEid The source endpoint id.
     * @param amount The amount to inflow.
     */
    function inflow(bytes32 tokenId, address tokenAddress, uint32 srcEid, uint256 amount) external;
}
"
    },
    "contracts/RateLimiter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { IRateLimiter, RateLimitConfig, RateLimit } from "./interfaces/IRateLimiter.sol";

/**
 * @title RateLimiter
 * @notice This contract implements a rate limiter for outbound and inbound flows of tokens.
 * It allows the owner to configure rate limits for specific token IDs and destination endpoint IDs.
 * The contract also provides functions to check available amounts for sending and receiving tokens,
 * as well as to perform outflow and inflow operations while respecting the configured rate limits.
 */
contract Test_RateLimiter is Ownable, IRateLimiter {
    // @dev Mappings for storing rate limits for outbound and inbound flows.
    mapping(bytes32 tokenId => mapping(uint32 eid => RateLimit)) public outboundLimits;
    mapping(bytes32 tokenId => mapping(uint32 eid => RateLimit)) public inboundLimits;

    // @dev The address of the Messenger contract, which is used to verify the caller in outflow and inflow functions.
    address public messenger;

    // @dev Modifier to control who can call inflow and outflow
    modifier onlyMessenger() {
        if (msg.sender != messenger) revert OnlyMessenger(msg.sender);
        _;
    }

    constructor(address _owner) Ownable(_owner) {}

    /**
     * @notice Sets the Messenger address, which is used to verify the caller in outflow and inflow functions.
     * @param _messenger The address of the Messenger contract.
     */
    function setMessenger(address _messenger) external onlyOwner {
        if (messenger == _messenger) revert MessengerIdempotent();
        messenger = _messenger;
        emit MessengerSet(_messenger);
    }

    /**
     * @notice Configures the rate limits for the specified tokenId and destination endpoint id.
     * @param _configs An array of `RateLimitConfig` structs representing the rate limit configurations.
     * - `remoteEid`: The destination endpoint id.
     * - `tokenId`: The identifier for the token.
     * - `outboundLimit`: This represents the maximum allowed amount within a given window for outflows.
     * - `outboundWindow`: Defines the duration of the rate limiting window for outflows.
     * - `inboundLimit`: This represents the maximum allowed amount within a given window for inflows.
     * - `inboundWindow`: Defines the duration of the rate limiting window for inflows.
     */
    function configureRateLimits(RateLimitConfig[] calldata _configs) external onlyOwner {
        for (uint256 i = 0; i < _configs.length; ++i) {
            RateLimitConfig memory cfg = _configs[i];
            // Configure outbound limit
            _configureRateLimit(outboundLimits[cfg.tokenId][cfg.remoteEid], cfg.outboundLimit, cfg.outboundWindow);
            // Configure inbound limit
            _configureRateLimit(inboundLimits[cfg.tokenId][cfg.remoteEid], cfg.inboundLimit, cfg.inboundWindow);
        }

        emit RateLimitsSet(_configs);
    }

    /**
     * @dev Configures a rate limit with new limit and window values while preserving decayed amount.
     * @param _rateLimit The rate limit to configure.
     * @param _newLimit The new limit value.
     * @param _newWindow The new window value.
     */
    function _configureRateLimit(RateLimit storage _rateLimit, uint256 _newLimit, uint256 _newWindow) internal {
        // Checkpoints the current amount by applying the previous rateLimit before we update it
        (, /*uint256 available*/ uint256 decayedAmount) = _getAmountAvailable(_rateLimit);

        // Update rate limit configuration
        _rateLimit.amount = decayedAmount;
        _rateLimit.limit = _newLimit;
        _rateLimit.window = _newWindow;
        _rateLimit.lastUpdated = block.timestamp;
    }

    /**
     * @notice Current amount that can be sent to this dst endpoint id for the given rate limit window and tokenId.
     * @param _tokenId The identifier for which the rate limit is being checked.
     * @dev _tokenAddress The address of the token from the corresponding Id.
     * @param _remoteEid The remote endpoint id.
     * @return sendable The current amount that can be sent.
     * @return currentOutbound The current amount used for outbound flows.
     * @return receivable The amount that can be received.
     * @return currentInbound The current amount used for inbound flows.
     */
    function getAmountsAvailable(
        bytes32 _tokenId,
        address /*_tokenAddress*/,
        uint32 _remoteEid
    ) external view returns (uint256 sendable, uint256 currentOutbound, uint256 receivable, uint256 currentInbound) {
        (sendable, currentOutbound) = _getAmountAvailable(outboundLimits[_tokenId][_remoteEid]);
        (receivable, currentInbound) = _getAmountAvailable(inboundLimits[_tokenId][_remoteEid]);
    }

    /**
     * @dev Gets the available amount and decayed amount used for a given rate limit after applying decay.
     * @param _rateLimit The rate limit to check.
     * @return available The available amount that can be used within the rate limit.
     * @return decayedAmount The current amount after applying decay.
     */
    function _getAmountAvailable(
        RateLimit storage _rateLimit
    ) internal view returns (uint256 available, uint256 decayedAmount) {
        decayedAmount = _calculateDecay(_rateLimit.amount, _rateLimit.lastUpdated, _rateLimit.limit, _rateLimit.window);
        available = _rateLimit.limit > decayedAmount ? _rateLimit.limit - decayedAmount : 0;
    }

    /**
     * @notice Verifies whether the specified amount falls within the rate limit constraints for the targeted
     * endpoint ID. On successful verification, it updates amountUsed and lastUpdated. If the amount exceeds
     * the rate limit, the operation reverts.
     * @param _tokenId The identifier for which the rate limit is being checked.
     * @dev _tokenAddress The address of the token from the corresponding Id.
     * @param _dstEid The destination endpoint id.
     * @param _amount The amount to outflow.
     */
    function outflow(
        bytes32 _tokenId,
        address /*_tokenAddress*/,
        uint32 _dstEid,
        uint256 _amount
    ) external onlyMessenger {
        _updateRateLimits(outboundLimits[_tokenId][_dstEid], inboundLimits[_tokenId][_dstEid], _amount);
    }

    /**
     * @notice To be used when you want to calculate your rate limits as a function of net outbound AND inbound.
     * @param _tokenId The identifier for which the rate limit is being checked.
     * @dev _tokenAddress The address of the token from the corresponding Id.
     * @param _srcEid The source endpoint id.
     * @param _amount The amount to inflow.
     */
    function inflow(
        bytes32 _tokenId,
        address /*_tokenAddress*/,
        uint32 _srcEid,
        uint256 _amount
    ) external onlyMessenger {
        _updateRateLimits(inboundLimits[_tokenId][_srcEid], outboundLimits[_tokenId][_srcEid], _amount);
    }

    /**
     * @dev Updates both primary and secondary rate limits for a flow operation.
     * @param _limitA The primary rate limit to update (outbound for outflow, inbound for inflow).
     * @param _limitB The secondary rate limit to update (inbound for outflow, outbound for inflow).
     * @param _amount The amount of the flow operation.
     */
    function _updateRateLimits(RateLimit storage _limitA, RateLimit storage _limitB, uint256 _amount) internal {
        // 1) Process primary limit - check availability and update
        (uint256 availableAmountA, uint256 decayedAmountA) = _getAmountAvailable(_limitA);
        if (_amount > availableAmountA) revert RateLimitExceeded();
        _limitA.amount = decayedAmountA + _amount;
        _limitA.lastUpdated = block.timestamp;

        // 2) Process secondary limit - update with subtraction
        (, /*uint256 availableAmountB*/ uint256 decayedAmountB) = _getAmountAvailable(_limitB);
        _limitB.amount = decayedAmountB > _amount ? decayedAmountB - _amount : 0;
        _limitB.lastUpdated = block.timestamp;
    }

    /**
     * @dev Calculates the decay of the amount based on the time elapsed since the last update.
     * @param _amount The current amount tracked against the rate limit.
     * @param _lastUpdated The timestamp of the last update.
     * @param _limit The maximum allowed amount within a given window.
     * @param _window The duration of the rate limiting window.
     * @return decayedAmount The decayed amount after applying the decay based on elapsed time.
     */
    function _calculateDecay(
        uint256 _amount,
        uint256 _lastUpdated,
        uint256 _limit,
        uint256 _window
    ) internal view returns (uint256 decayedAmount) {
        uint256 elapsed = block.timestamp - _lastUpdated;
        // @dev if window is set to 0, then the full decay is basically immediate
        uint256 decay = (_limit * elapsed) / (_window > 0 ? _window : 1);
        decayedAmount = _amount > decay ? _amount - decay : 0;
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
Multisig, Multi-Signature, Factory|addr:0x768b501e05760e31775da59bce8a42047632a05d|verified:true|block:23743151|tx:0x35f7fcf9202b040a8c479eabf77dda1ee4422610249c70c5be588ac51a29818b|first_check:1762509766

Submitted on: 2025-11-07 11:02:47

Comments

Log in to comment.

No comments yet.