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
}
}
}}
Submitted on: 2025-11-07 11:02:47
Comments
Log in to comment.
No comments yet.