Source Code
Overview
ETH Balance
0 ETH
Eth Value
$0.00Latest 9 from a total of 9 transactions
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Renounce Role | 22819398 | 242 days ago | IN | 0 ETH | 0.00023531 | ||||
| Grant Role | 22819397 | 242 days ago | IN | 0 ETH | 0.00054195 | ||||
| Grant Role | 22819384 | 242 days ago | IN | 0 ETH | 0.00078745 | ||||
| Revoke Role | 22819382 | 242 days ago | IN | 0 ETH | 0.00025456 | ||||
| Revoke Role | 22819381 | 242 days ago | IN | 0 ETH | 0.00028938 | ||||
| Revoke Role | 22819378 | 242 days ago | IN | 0 ETH | 0.00025095 | ||||
| Grant Role | 22819377 | 242 days ago | IN | 0 ETH | 0.00061012 | ||||
| Set Default User... | 22819376 | 242 days ago | IN | 0 ETH | 0.00056527 | ||||
| Set Global Limit... | 22819375 | 242 days ago | IN | 0 ETH | 0.0005626 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
TokenManagerRegistrar
Compiler Version
v0.8.16+commit.07a7930e
Optimization Enabled:
Yes with 200 runs
Other Settings:
london EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
import "contracts/globalMarkets/tokenManager/GMTokenManager.sol";
import "contracts/xManager/OndoRateLimiter.sol";
import "contracts/globalMarkets/interfaces/IRegistrar.sol";
import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "contracts/external/openzeppelin/contracts/security/Pausable.sol";
/**
* @title TokenManagerRegistrar
* @author Ondo Finance
* @notice This contract is responsible for registering new tokens with the GM Token Management
* system. It allows for the registration of new tokens, setting the GM Token Manager,
* and configuring rate limits for those tokens.
*/
contract TokenManagerRegistrar is
IRegistrar,
AccessControlEnumerable,
Pausable
{
/// Role for changing the token name, symbol, compliance and token pause manager
bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
/// Role for the token factory that can register new tokens
bytes32 public constant TOKEN_FACTORY_ROLE = keccak256("TOKEN_FACTORY_ROLE");
/// Role allowed to pause the registrar
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// Role allowed to unpause the registrar
bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");
/// Struct containing rate limit configuration values
struct RateLimitConfig {
uint256 subscriptionLimit;
uint256 redemptionLimit;
uint48 subscriptionWindow;
uint48 redemptionWindow;
}
/// Global rate limit configuration
RateLimitConfig public globalLimits;
/// Default user rate limit configuration
RateLimitConfig public defaultUserLimits;
/// Address of the GM Token Manager that will handle minting/redeeming
GMTokenManager public gmTokenManager;
/// Address of the rate limiter that will handle token limits
OndoRateLimiter public ondoRateLimiter;
/**
* @notice Constructor to initialize the contract with the GM Token Manager and rate limiter
* @param guardian The address of the admin account that begins with the default admin role
* @param _gmTokenManager The address of the GM Token Manager contract
* @param _ondoRateLimiter The address of the Ondo Rate Limiter contract
*/
constructor(
address guardian,
address _gmTokenManager,
address _ondoRateLimiter
) {
if (_gmTokenManager == address(0)) revert GMTokenManagerCantBeZero();
if (_ondoRateLimiter == address(0)) revert RateLimiterCantBeZero();
gmTokenManager = GMTokenManager(_gmTokenManager);
ondoRateLimiter = OndoRateLimiter(_ondoRateLimiter);
_grantRole(DEFAULT_ADMIN_ROLE, guardian);
_grantRole(CONFIGURER_ROLE, guardian);
_grantRole(PAUSER_ROLE, guardian);
_grantRole(UNPAUSER_ROLE, guardian);
}
/**
* @notice Pauses the registrar, disabling registration
*/
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @notice Unpauses the registrar, enabling registration
*/
function unpause() external onlyRole(UNPAUSER_ROLE) {
_unpause();
}
/**
* @notice Registers a new token with the GM Token Manager and configures rate limits
* @param token The address of the token to register
* @dev Only callable by accounts with TOKEN_FACTORY_ROLE
*/
function register(
address token
) external override onlyRole(TOKEN_FACTORY_ROLE) whenNotPaused {
if (token == address(0)) revert TokenAddressCantBeZero();
gmTokenManager.setGMTokenRegistrationStatus(token, true);
// Grant minter role to GM Token Manager
IAccessControlEnumerable(token).grantRole(
keccak256("MINTER_ROLE"),
address(gmTokenManager)
);
// Configure rate limiter for token
ondoRateLimiter.setGlobalSubscriptionLimit(
token,
globalLimits.subscriptionLimit,
globalLimits.subscriptionWindow
);
ondoRateLimiter.setGlobalRedemptionLimit(
token,
globalLimits.redemptionLimit,
globalLimits.redemptionWindow
);
ondoRateLimiter.setDefaultUserSubscriptionLimitConfig(
token,
defaultUserLimits.subscriptionLimit,
defaultUserLimits.subscriptionWindow
);
ondoRateLimiter.setDefaultUserRedemptionLimitConfig(
token,
defaultUserLimits.redemptionLimit,
defaultUserLimits.redemptionWindow
);
emit TokenRegistered(token);
}
/**
* @notice Sets or updates the GM Token Manager address
* @param _gmTokenManager The new GM Token Manager address
*/
function setGMTokenManager(
address _gmTokenManager
) external onlyRole(CONFIGURER_ROLE) {
if (_gmTokenManager == address(0)) revert GMTokenManagerCantBeZero();
emit GMTokenManagerSet(address(gmTokenManager), _gmTokenManager);
gmTokenManager = GMTokenManager(_gmTokenManager);
}
/**
* @notice Sets or updates the rate limiter address
* @param _rateLimiter The new rate limiter address
*/
function setRateLimiter(
address _rateLimiter
) external onlyRole(CONFIGURER_ROLE) {
if (_rateLimiter == address(0)) revert RateLimiterCantBeZero();
emit RateLimiterSet(address(ondoRateLimiter), _rateLimiter);
ondoRateLimiter = OndoRateLimiter(_rateLimiter);
}
/**
* @notice Sets the global rate limit configurations for mints and redemptions
* @param subscriptionLimit Global subscription limit amount in USD with 18 decimals
* @param subscriptionWindow Global subscription time window in seconds
* @param redemptionLimit Global redemption limit amount in USD with 18 decimals
* @param redemptionWindow Global redemption time window in seconds
*/
function setGlobalLimitConfigs(
uint256 subscriptionLimit,
uint48 subscriptionWindow,
uint256 redemptionLimit,
uint48 redemptionWindow
) external onlyRole(CONFIGURER_ROLE) {
globalLimits = RateLimitConfig({
subscriptionLimit: subscriptionLimit,
redemptionLimit: redemptionLimit,
subscriptionWindow: subscriptionWindow,
redemptionWindow: redemptionWindow
});
emit GlobalLimitConfigsSet(
subscriptionLimit,
subscriptionWindow,
redemptionLimit,
redemptionWindow
);
}
/**
* @notice Sets the default user rate limit configurations for mints and redemptions
* @param subscriptionLimit Default user subscription limit amount in USD with 18 decimals
* @param subscriptionWindow Default user subscription time window in seconds
* @param redemptionLimit Default user redemption limit amount in USD with 18 decimals
* @param redemptionWindow Default user redemption time window in seconds
*/
function setDefaultUserLimitConfigs(
uint256 subscriptionLimit,
uint48 subscriptionWindow,
uint256 redemptionLimit,
uint48 redemptionWindow
) external onlyRole(CONFIGURER_ROLE) {
defaultUserLimits = RateLimitConfig({
subscriptionLimit: subscriptionLimit,
redemptionLimit: redemptionLimit,
subscriptionWindow: subscriptionWindow,
redemptionWindow: redemptionWindow
});
emit DefaultUserLimitConfigsSet(
subscriptionLimit,
subscriptionWindow,
redemptionLimit,
redemptionWindow
);
}
/**
* @notice Emitted when the `GMTokenManager` is set
* @param oldManager The old `GMTokenManager` address
* @param newManager The new `GMTokenManager` address
*/
event GMTokenManagerSet(
address indexed oldManager,
address indexed newManager
);
/**
* @notice Emitted when the `OndoRateLimiter` address is updated
* @param oldRateLimiter The old rate limiter address
* @param newRateLimiter The new rate limiter address
*/
event RateLimiterSet(
address indexed oldRateLimiter,
address indexed newRateLimiter
);
/**
* @notice Emitted when the global limit configurations used for new tokens are updated
* @param subscriptionLimit The new global subscription limit amount in USD with 18 decimals
* @param subscriptionWindow The new global subscription time window in seconds
* @param redemptionLimit The new global redemption limit amount in USD with 18 decimals
* @param redemptionWindow The new global redemption time window in seconds
*/
event GlobalLimitConfigsSet(
uint256 subscriptionLimit,
uint48 subscriptionWindow,
uint256 redemptionLimit,
uint48 redemptionWindow
);
/**
* @notice Emitted when the default user limit configurations used for new tokens are updated
* @param subscriptionLimit The new default user subscription limit amount in USD with 18 decimals
* @param subscriptionWindow The new default user subscription time window in seconds
* @param redemptionLimit The new default user redemption limit amount in USD with 18 decimals
* @param redemptionWindow The new default user redemption time window in seconds
*/
event DefaultUserLimitConfigsSet(
uint256 subscriptionLimit,
uint48 subscriptionWindow,
uint256 redemptionLimit,
uint48 redemptionWindow
);
/**
* @notice Emitted when a new token is registered
* @param token The address of the token that was registered following a deployment
*/
event TokenRegistered(address indexed token);
/// Error thrown when attempting to set the GM Token Manager to zero address
error GMTokenManagerCantBeZero();
/// Error thrown when attempting to set the rate limiter to zero address
error RateLimiterCantBeZero();
/// Error thrown when attempting to register a token with zero address
error TokenAddressCantBeZero();
}/**SPDX-License-Identifier: BUSL-1.1
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
import "contracts/globalMarkets/interfaces/IIssuanceHours.sol";
import "contracts/xManager/interfaces/IOndoIDRegistry.sol";
import "contracts/xManager/interfaces/IOndoRateLimiter.sol";
import "contracts/interfaces/IRWALike.sol";
import "contracts/globalMarkets/tokenManager/IGMTokenManagerErrors.sol";
import "contracts/globalMarkets/tokenManager/IGMTokenManagerEvents.sol";
import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "contracts/external/openzeppelin/contracts/security/ReentrancyGuard.sol";
import "contracts/external/openzeppelin/contracts/token/SafeERC20.sol";
import "contracts/external/openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "contracts/external/openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "contracts/globalMarkets/tokenManager/IGMTokenManager.sol";
import "contracts/globalMarkets/interfaces/IOndoSanityCheckOracle.sol";
import "contracts/globalMarkets/onUSDManager/IonUSDManager.sol";
/**
* @title GMTokenManager
* @author Ondo Finance
* @notice Contract for managing tokenized equity assets
* @notice The GMTokenManager contract contains the core logic for processing minting
* and redemptions of GM tokens.
* The responsibilities of this contract are:
* - Verifying attestation signatures
* - Calculating the amount of GM tokens and onUSD to mint and burn
* based on the quote attestation that was received
* - Performing appropriate sanity checks on the quote attestation
* - Enforcing the minimum deposit and redemption amounts and ensuring the
* quote price is in line with the rough oracle price
* - Ensuring users are registered with the OndoIDRegistry
* - Checking user-specific and global rate limits
*/
contract GMTokenManager is
EIP712,
IGMTokenManager,
IGMTokenManagerEvents,
IGMTokenManagerErrors,
ReentrancyGuard,
AccessControlEnumerable
{
using SafeERC20 for IERC20;
// rwaTokenAddress for use in the OndoIDRegistry to validate KYC for all GM issuance
address public gmIdentifier =
address(uint160(uint256(keccak256(abi.encodePacked("global_markets")))));
bytes32 public constant ATTESTATION_SIGNER_ROLE =
keccak256("ATTESTATION_SIGNER_ROLE");
bytes32 public constant QUOTE_TYPEHASH =
keccak256(
"Quote(uint256 chainId,uint256 attestationId,bytes32 userId,address asset,uint256 price,uint256 quantity,uint256 expiration,uint8 side,bytes32 additionalData)"
);
/// Mapping of attestation IDs to whether they have been redeemed
/// @dev This is used to prevent replay attacks on the same attestation ID
mapping(uint256 => bool) public executedAttestationIds;
/// Monotonically increasing ID for each execution of a quote
uint256 public executionId = 0;
/// The decimals normalizer for the GM token
uint256 public constant GM_NORMALIZER = 1e18;
/// Role to configure the contract
bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
/// Role to pause minting and redemptions
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// Role to unpause functionality
bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");
/// Role to manually service a mint of GM tokens
bytes32 public constant ADMIN_MINT_ROLE = keccak256("ADMIN_MINT_ROLE");
/// The token that can be used to subscribe to a GM token and can be received when redeeming a GM token
address public immutable onUsd;
/// The address of the onUSD manager used for swapping with other tokens
IonUSDManager public onUSDManager;
/**
* @notice Minimum USD amount required to subscribe to a GM token, denoted in USD with 18
* decimals.
*/
uint256 public minimumDepositUSD;
/**
* @notice Minimum USD amount required to perform an GM token redemption to be allowed,
* denoted in USD with 18 decimals
*/
uint256 public minimumRedemptionUSD;
/// Whether mints are paused for this contract
bool public globalMintingPaused;
/// Whether redemptions are paused for this contract
bool public globalRedeemingPaused;
/// Whether mints are paused for a specific gm asset
mapping(address => bool) public gmTokenMintingPaused;
/// Whether redemptions are paused for a specific gm asset
mapping(address => bool) public gmTokenRedemptionsPaused;
/// The `OndoIDRegistry` contract address
IOndoIDRegistry public ondoIDRegistry;
/// The `OndoRateLimiter` contract address
IOndoRateLimiter public ondoRateLimiter;
/// The `IssuanceHours` contract for managing marketing hours
IIssuanceHours public issuanceHours;
/// The `OndoSanityCheckOracle` contract for validating the price of the GM token
IOndoSanityCheckOracle public sanityCheckOracle;
// Map of all GM tokens that are accepted for minting and redemptions
mapping(address => bool) public gmTokenAccepted;
/**
* @param _defaultAdmin The default admin role for the contract
* @param _minimumDepositUSD The minimum subscription amount, denoted in USD with 18 decimals
* @param _minimumRedemptionUSD The minimum redemption amount, denoted in USD with 18 decimals
*/
constructor(
address _defaultAdmin,
address _onUsd,
uint256 _minimumDepositUSD,
uint256 _minimumRedemptionUSD
) EIP712("OndoGMTokenManager", "1") {
if (_onUsd == address(0)) revert OnUSDAddressCantBeZero();
onUsd = _onUsd;
minimumDepositUSD = _minimumDepositUSD;
minimumRedemptionUSD = _minimumRedemptionUSD;
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
}
/**
* @notice Called by users to mint GM tokens with a quote attestation using onUSD
* @param quote The quote to mint GM tokens with
* @param signature The signature of the quote attestation
* @param depositToken The token the user would like to deposit in return for GM Tokens.
* If not onUSD, this will be reliant on the ability to swap for onUSD in th onUSDManager
* @param depositTokenAmount The amount of depositToken the user wishes to spend to purchase onUSD atomically in the transaction
* @return The amount of GM tokens minted to the user
* @dev If the deposit token is not onUSD and the depositTokenAmount is not enough to swap for onUSD
* worth quantity * price as specified in the quote, the transaction will fail as the `mintOnusdValue`
* is passed in to the minimum RWA received field in `subscribe`.
*/
function mintWithAttestation(
Quote calldata quote,
bytes memory signature,
address depositToken,
uint256 depositTokenAmount
) public override whenMintingNotPaused(quote.asset) returns (uint256) {
if (depositToken == address(0)) revert TokenAddressCantBeZero();
if (quote.side != QuoteSide.BUY) revert InvalidQuoteSide();
bytes32 userId = ondoIDRegistry.getRegisteredID(gmIdentifier, _msgSender());
if (userId == bytes32(0)) revert UserNotRegistered();
_verifyQuote(quote, signature, userId);
executedAttestationIds[quote.attestationId] = true;
// USD values are normalized to 18 decimals
uint256 mintOnusdValue = (quote.quantity * quote.price) / GM_NORMALIZER;
if (mintOnusdValue < minimumDepositUSD) revert DepositAmountTooSmall();
// Transfer onUsd into the contract
if (depositToken == address(onUsd)) {
IERC20(onUsd).safeTransferFrom(
_msgSender(),
address(this),
mintOnusdValue
);
} else {
if (address(onUSDManager) == address(0)) revert OnUSDManagerNotEnabled();
IERC20(depositToken).safeTransferFrom(
_msgSender(),
address(this),
depositTokenAmount
);
IERC20(depositToken).forceApprove(
address(onUSDManager),
depositTokenAmount
);
uint256 receivedOnUsd = onUSDManager.subscribe(
depositToken,
depositTokenAmount,
mintOnusdValue
);
// If the deposit token is not onUSD, we need to refund the user if they sent more than
// the required amount of onUSD to mint the GM tokens
uint256 refund = receivedOnUsd - mintOnusdValue;
if (refund > 0) {
IERC20(onUsd).safeTransfer(_msgSender(), refund);
}
}
IRWALike(onUsd).burn(mintOnusdValue);
ondoRateLimiter.checkAndUpdateRateLimit(
IOndoRateLimiter.TransactionType.SUBSCRIPTION,
quote.asset,
userId,
mintOnusdValue
);
// actually mint the token to the user
IRWALike(quote.asset).mint(_msgSender(), quote.quantity);
_emitTradeExecuted(quote);
return quote.quantity;
}
/**
* @notice Called by users to redeem GM tokens for onUSD
* @param quote The quote to redeem GM tokens with
* @param signature The signature of the quote attestation
* @param receiveToken The token the user would like to receive. If it is not onUSD,
* transaction success will depend on liquidity in onUSDManager
* @param minimumReceiveAmount The minimum amount of tokens the user would like to receive
* - ONLY applicable the the user requests a non-onUSD otoken
* @return The amount of onUSD minted
*/
function redeemWithAttestation(
Quote calldata quote,
bytes memory signature,
address receiveToken,
uint256 minimumReceiveAmount
) public override whenRedeemNotPaused(quote.asset) returns (uint256) {
if (receiveToken == address(0)) revert TokenAddressCantBeZero();
if (quote.side != QuoteSide.SELL) revert InvalidQuoteSide();
bytes32 userId = ondoIDRegistry.getRegisteredID(gmIdentifier, _msgSender());
if (userId == bytes32(0)) revert UserNotRegistered();
_verifyQuote(quote, signature, userId);
executedAttestationIds[quote.attestationId] = true;
// burn the GM tokens
IERC20(quote.asset).safeTransferFrom(
_msgSender(),
address(this),
quote.quantity
);
IRWALike(quote.asset).burn(quote.quantity);
// USD values are normalized to 18 decimals
uint256 redemptionOnusdValue = (quote.quantity * quote.price) /
GM_NORMALIZER;
if (redemptionOnusdValue < minimumRedemptionUSD)
revert RedemptionAmountTooSmall();
ondoRateLimiter.checkAndUpdateRateLimit(
IOndoRateLimiter.TransactionType.REDEMPTION,
quote.asset,
userId,
redemptionOnusdValue
);
// Mint onUSD to the contract
IRWALike(onUsd).mint(address(this), redemptionOnusdValue);
if (receiveToken == address(onUsd)) {
IERC20(onUsd).safeTransfer(_msgSender(), redemptionOnusdValue);
} else {
if (address(onUSDManager) == address(0)) revert OnUSDManagerNotEnabled();
IERC20(onUsd).approve(address(onUSDManager), redemptionOnusdValue);
uint256 receiveAmount = onUSDManager.redeem(
redemptionOnusdValue,
receiveToken,
minimumReceiveAmount
);
IERC20(receiveToken).safeTransfer(_msgSender(), receiveAmount);
}
_emitTradeExecuted(quote);
return redemptionOnusdValue;
}
/**
* @notice Admin function to service a subscription whose corresponding deposit been made outside
* of this contracts system. This is almost identical to the `_processMint`
* function, but skips the deposit token transfer and fee calculation (fees are
* managed off-chain). The mint and/or transfer itself must be done in the child contract
* implementation.
* @param recipient The address to send the GM tokens to
* @param gmTokenAmount The amount of GM tokens to mint and/or transfer
* @param metadata Additional metadata to emit with the subscription
*/
function adminProcessMint(
address gmToken,
address recipient,
uint256 gmTokenAmount,
bytes32 metadata
) external whenMintingNotPaused(gmToken) onlyRole(ADMIN_MINT_ROLE) {
if (gmTokenAccepted[gmToken] == false) revert GMTokenNotRegistered();
bytes32 userId = ondoIDRegistry.getRegisteredID(gmIdentifier, recipient);
if (userId == bytes32(0)) revert UserNotRegistered();
// Actually mint the token to the user
IRWALike(gmToken).mint(recipient, gmTokenAmount);
emit AdminMint(recipient, userId, gmToken, gmTokenAmount, metadata);
}
/**
* @notice Sets the `IssuanceHours` contract
* @param _issuanceHours The `IssuanceHours` contract address
*/
function setIssuanceHours(
address _issuanceHours
) external onlyRole(CONFIGURER_ROLE) {
if (_issuanceHours == address(0)) revert IssuanceHoursAddressCantBeZero();
emit IssuanceHoursSet(address(issuanceHours), _issuanceHours);
issuanceHours = IIssuanceHours(_issuanceHours);
}
/**
* @notice Sets the `OndoSanityCheckOracle` contract
* @param _sanityCheckOracle The new `OndoSanityCheckOracle` contract address
*/
function setSanityCheckOracle(
address _sanityCheckOracle
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_sanityCheckOracle == address(0))
revert SanityCheckOracleAddressCantBeZero();
emit OndoSanityCheckOracleSet(
address(sanityCheckOracle),
_sanityCheckOracle
);
sanityCheckOracle = IOndoSanityCheckOracle(_sanityCheckOracle);
}
/**
* @notice Sets the `OndoIDRegistry` contract
* @param _ondoIDRegistry The `OndoIDRegistry` contract address
*/
function setOndoIDRegistry(
address _ondoIDRegistry
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_ondoIDRegistry == address(0)) revert IDRegistryAddressCantBeZero();
emit OndoIDRegistrySet(address(ondoIDRegistry), _ondoIDRegistry);
ondoIDRegistry = IOndoIDRegistry(_ondoIDRegistry);
// Ensure that the `OndoIDRegistry` interface is supported
ondoIDRegistry.getRegisteredID(gmIdentifier, address(this));
}
/**
* @notice Sets the `OndoRateLimiter` contract
* @param _ondoRateLimiter The `OndoRateLimiter` contract address
*/
function setOndoRateLimiter(
address _ondoRateLimiter
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_ondoRateLimiter == address(0)) revert RateLimiterAddressCantBeZero();
emit OndoRateLimiterSet(address(ondoRateLimiter), _ondoRateLimiter);
ondoRateLimiter = IOndoRateLimiter(_ondoRateLimiter);
}
/**
* @notice Sets the `onUSDManager` contract
* @param _onUSDManager The `onUSDManager` contract address
*/
function setOnusdManager(
address _onUSDManager
) external onlyRole(DEFAULT_ADMIN_ROLE) {
emit OnUSDManagerSet(address(onUSDManager), _onUSDManager);
onUSDManager = IonUSDManager(_onUSDManager);
}
/**
* @notice Sets whether a token is accepted for mints and redemptions on this contract
* @param token The address of the token
* @param accepted Whether the token is accepted for mints and redemptions
*/
function setGMTokenRegistrationStatus(
address token,
bool accepted
) external onlyRole(CONFIGURER_ROLE) {
if (token == address(0)) revert TokenAddressCantBeZero();
gmTokenAccepted[token] = accepted;
emit GMTokenRegistered(token, accepted);
}
/**
* @notice Sets the minimum amount required for a subscription
* @param _minimumDepositUSD The minimum amount required to subscribe, denoted in
* USD with 18 decimals
*/
function setMinimumDepositAmount(
uint256 _minimumDepositUSD
) external onlyRole(CONFIGURER_ROLE) {
emit MinimumDepositAmountSet(minimumDepositUSD, _minimumDepositUSD);
minimumDepositUSD = _minimumDepositUSD;
}
/**
* @notice Sets the minimum amount to redeem
* @param _minimumRedemptionUSD The minimum amount required to redeem,
* denoted in USD with 18.
*/
function setMinimumRedemptionAmount(
uint256 _minimumRedemptionUSD
) external onlyRole(CONFIGURER_ROLE) {
emit MinimumRedemptionAmountSet(
minimumRedemptionUSD,
_minimumRedemptionUSD
);
minimumRedemptionUSD = _minimumRedemptionUSD;
}
/**
* @notice Rescue and transfer tokens locked in this contract
* @param token The address of the token
* @param to The address of the recipient
* @param amount The amount of token to transfer
*/
function retrieveTokens(
address token,
address to,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
IERC20(token).safeTransfer(to, amount);
}
/**
* @notice Returns the domain separator for EIP712
* @return The domain separator
*/
function getDomainSeparator() public view returns (bytes32) {
return _domainSeparatorV4();
}
/*//////////////////////////////////////////////////////////////
Pause/Unpause
//////////////////////////////////////////////////////////////*/
/// Globally pause the minting functionality.
function pauseGlobalMints() external onlyRole(PAUSER_ROLE) {
globalMintingPaused = true;
emit GlobalMintingPaused();
}
/// Globally unpause the minting functionality.
function unpauseGlobalMints() external onlyRole(UNPAUSER_ROLE) {
globalMintingPaused = false;
emit GlobalMintingUnpaused();
}
/// Globally pause the redeem functionality.
function pauseGlobalRedeems() external onlyRole(PAUSER_ROLE) {
globalRedeemingPaused = true;
emit GlobalRedeemingPaused();
}
/// Globally unpause the redeem functionality.
function unpauseGlobalRedeems() external onlyRole(UNPAUSER_ROLE) {
globalRedeemingPaused = false;
emit GlobalRedeemingUnpaused();
}
/**
* @notice Pauses minting for a specific GM token
* @param gmToken The address of the GM token
*/
function pauseGMTokenMints(address gmToken) external onlyRole(PAUSER_ROLE) {
gmTokenMintingPaused[gmToken] = true;
emit GMTokenMintingPaused(gmToken);
}
/**
* @notice Unpauses minting for a specific GM token
* @param gmToken The address of the GM token
*/
function unpauseGMTokenMints(
address gmToken
) external onlyRole(UNPAUSER_ROLE) {
gmTokenMintingPaused[gmToken] = false;
emit GMTokenMintingUnpaused(gmToken);
}
/**
* @notice Pauses redemption for a specific GM token
* @param gmToken The address of the GM token
*/
function pauseGMTokenRedemptions(
address gmToken
) external onlyRole(PAUSER_ROLE) {
gmTokenRedemptionsPaused[gmToken] = true;
emit GMTokenRedeemingPaused(gmToken);
}
/**
* @notice Unpauses redemption for a specific GM token
* @param gmToken The address of the GM token
*/
function unpauseGMTokenRedemptions(
address gmToken
) external onlyRole(UNPAUSER_ROLE) {
gmTokenRedemptionsPaused[gmToken] = false;
emit GMTokenRedeemingUnpaused(gmToken);
}
/**
* @notice Verifies the signature of the quote
* @param quote The quote to verify
* @param signature The signature to verify
* @param userId The user ID of the user executing the quote
*/
function _verifyQuote(
Quote calldata quote,
bytes memory signature,
bytes32 userId
) internal {
// Ensure the attestation isn't being reused
if (executedAttestationIds[quote.attestationId])
revert AttestationAlreadyExecuted();
if (block.timestamp > quote.expiration) revert AttestationExpired();
if (userId != quote.userId) revert UserIdMismatch(userId, quote.userId);
issuanceHours.checkIsValidHours();
sanityCheckOracle.validatePrice(quote.asset, quote.price);
if (gmTokenAccepted[quote.asset] == false) revert GMTokenNotRegistered();
if (quote.chainId != block.chainid) revert InvalidChainId();
bytes32 digest = _hashTypedDataV4(
keccak256(
abi.encode(
QUOTE_TYPEHASH,
quote.chainId,
quote.attestationId,
quote.userId,
quote.asset,
quote.price,
quote.quantity,
quote.expiration,
quote.side,
quote.additionalData
)
)
);
address signer = ECDSA.recover(digest, signature);
if (!hasRole(ATTESTATION_SIGNER_ROLE, signer))
revert InvalidAttestationSigner();
}
/**
* @notice Emits a `TradeExecuted` event and increments the `executionId`
* @param quote The quote to emit the event for
* @dev The only purpose of this is to prevent stack too deep errors from unpacking the large struct
*/
function _emitTradeExecuted(Quote calldata quote) internal {
emit TradeExecuted(
++executionId,
quote.attestationId,
quote.chainId,
quote.userId,
quote.side,
quote.asset,
quote.price,
quote.quantity,
quote.expiration,
quote.additionalData
);
}
/// Ensure that the subscribe functionality is not paused
modifier whenMintingNotPaused(address gmToken) {
if (globalMintingPaused) revert GlobalMintsPaused();
if (gmTokenMintingPaused[gmToken]) revert GMTokenMintsPaused();
_;
}
/// Ensure that the redeem functionality is not paused
modifier whenRedeemNotPaused(address gmToken) {
if (globalRedeemingPaused) revert GlobalRedemptionsPaused();
if (gmTokenRedemptionsPaused[gmToken]) revert GMTokenRedemptionsPaused();
_;
}
}//SPDX-License-Identifier: MIT
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
import "contracts/xManager/interfaces/IOndoRateLimiter.sol";
import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";
/**
* @title OndoRateLimiter
* @author Ondo Finance
* @notice The OndoRateLimiter contract manages rate limits for subscriptions
* and redemptions. It allows the configuration of global rate limits for all users and
* specific rate limits for individual users. Even if a user has a specific rate limit,
* the global rate limit will always be respected. The rate limits are defined in
* terms of a maximum allowable amount (in USD with 18 decimals) within a specified window.
*/
contract OndoRateLimiter is IOndoRateLimiter, AccessControlEnumerable {
/// Role for the client contracts using this rate limiter
bytes32 public constant CLIENT_ROLE = keccak256("CLIENT_ROLE");
/// Role for the admin who can configure the rate limit state for users
bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
/**
* @notice Rate Limit struct
* @param capacityUsed Current amount (in USD with 18 decimals) within the rate limit window
* @param lastUpdated Timestamp (in seconds) representing the last time the rate limit was
* checked and updated
* @param limit This represents the maximum allowed amount (in USD with 18 decimals)
* within a given window
* @param window Defines the duration (in seconds) of the rate limiting window
*/
struct RateLimit {
uint256 capacityUsed;
uint256 lastUpdated;
uint256 limit;
uint48 window;
}
/**
* @notice Rate Limit configuration struct
* @param limit The maximum allowable amount (in USD with 18 decimals) within the specified window
* @param window The time window (in seconds) for which the limit applies
*/
struct RateLimitConfig {
uint256 limit;
uint48 window;
}
/// Global rate limits for subscriptions for each RWA token
mapping(address /* rwaToken */ => RateLimit) public globalSubscriptionLimits;
/// Global rate limits for redemptions for each RWA token
mapping(address /* rwaToken */ => RateLimit) public globalRedemptionLimits;
/// User-specific rate limits for subscriptions for each RWA token
mapping(address /* rwaToken */ => mapping(bytes32 /* user ID */ => RateLimit))
public userSubscriptionLimits;
/// User-specific rate limits for redemptions for each RWA token
mapping(address /* rwaToken */ => mapping(bytes32 /* user ID */ => RateLimit))
public userRedemptionLimits;
/// Default user rate limit configurations for subscriptions for each RWA token
mapping(address /* rwaToken */ => RateLimitConfig)
public defaultUserSubscriptionLimitConfigs;
/// Default user rate limit configurations for redemptions for each RWA token
mapping(address /* rwaToken */ => RateLimitConfig)
public defaultUserRedemptionLimitConfigs;
/**
* @notice Event emitted when `setGlobalSubscriptionLimit` or `setGlobalRedemptionLimit` is
* called
* @param transactionType The type of transaction (SUBSCRIPTION or REDEMPTION)
* the new limit applies to.
* @param rwaToken The address of the RWA token the new limit applies to
* @param limit The new maximum allowable amount (in USD) within the specified window
* @param window The new time window (in seconds) for which the limit applies
*/
event GlobalRateLimitSet(
TransactionType transactionType,
address rwaToken,
uint256 limit,
uint48 window
);
/**
* @notice Event emitted when `setDefaultUserSubscriptionLimitConfig` or
* `setDefaultUserRedemptionLimitConfig` is called
* @param transactionType The type of transaction (SUBSCRIPTION or REDEMPTION) the new
* limit applies to.
* @param rwaToken The address of the RWA token the new limit applies to
* @param limit The new maximum allowable amount (in USD) within the specified window
* @param window The new time window (in seconds) for which the limit applies
*/
event DefaultUserRateLimitSet(
TransactionType transactionType,
address rwaToken,
uint256 limit,
uint48 window
);
/**
* @notice Event emitted when `setUserSubscriptionRateLimit` or
* `setUserRedemptionRateLimit` is called
* @param transactionType The type of transaction (SUBSCRIPTION or REDEMPTION) the new limit applies to
* @param rwaToken The address of the RWA token the new limit applies to
* @param userID The ID of the user the new limit applies to
* @param limit The new maximum allowable amount (in USD) within the specified window
* @param window The new time window (in seconds) for which the limit applies
*/
event UserRateLimitSet(
TransactionType transactionType,
address rwaToken,
bytes32 userID,
uint256 limit,
uint48 window
);
/// Error thrown when an amount exceeds the rate limiter
error RateLimitExceeded();
/// Error thrown when the global rate limit is not set
error GlobalRateLimitNotSet();
/// Error thrown when the default user rate limit is not set
error DefaultUserRateLimitNotSet();
/// Error thrown when attempting to set a rate limit for an RWAToken with zero address
error RWAAddressCantBeZero();
/// Error thrown when attempting to set a rate limit for a user with zero ID
error UserIDCantBeZero();
/**
* @param guardian The address of the guardian who will be granted the default admin role
*/
constructor(address guardian) {
_grantRole(DEFAULT_ADMIN_ROLE, guardian);
}
/**
* @notice Checks and updates the rate limit for a given user and RWA token
* @param transactionType The type of transaction (SUBSCRIPTION or REDEMPTION)
* @param rwaToken The address of the RWA token being transacted
* @param userID The ID of the user
* @param usdValue The value of the transaction, in USD with 18 decimals
*/
function checkAndUpdateRateLimit(
TransactionType transactionType,
address rwaToken,
bytes32 userID,
uint256 usdValue
) external onlyRole(CLIENT_ROLE) {
RateLimit storage globalRl = transactionType == TransactionType.SUBSCRIPTION
? globalSubscriptionLimits[rwaToken]
: globalRedemptionLimits[rwaToken];
if (globalRl.lastUpdated == 0) revert GlobalRateLimitNotSet();
// Get global available capacity based on the rate limit configuration and time elapsed
(
uint256 globalCurrentCapacityUsed,
uint256 globalAvailableCapacity
) = _calculateDecay(
globalRl.capacityUsed,
globalRl.lastUpdated,
globalRl.limit,
globalRl.window
);
if (usdValue > globalAvailableCapacity) revert RateLimitExceeded();
RateLimit storage userRl = transactionType == TransactionType.SUBSCRIPTION
? userSubscriptionLimits[rwaToken][userID]
: userRedemptionLimits[rwaToken][userID];
// If the user rate limit has not been set, instantiate it with the default configuration
if (userRl.lastUpdated == 0)
_instantiateUserRateLimits(transactionType, rwaToken, userID);
// Get user's available capacity based on the rate limit configuration and time elapsed
(
uint256 userCurrentCapacityUsed,
uint256 userAvailableCapacity
) = _calculateDecay(
userRl.capacityUsed,
userRl.lastUpdated,
userRl.limit,
userRl.window
);
if (usdValue > userAvailableCapacity) revert RateLimitExceeded();
globalRl.capacityUsed = globalCurrentCapacityUsed + usdValue;
globalRl.lastUpdated = block.timestamp;
userRl.capacityUsed = userCurrentCapacityUsed + usdValue;
userRl.lastUpdated = block.timestamp;
}
/**
* @notice Instantiates the rate limit state for a new user based on the default configuration
* @param transactionType The type of transaction (SUBSCRIPTION or REDEMPTION) for which
* the rate limit is being set
* @param rwaToken The address of the RWA token for which the rate limit is being set
* @param userID The ID of the user for which the rate limit is being set
* @dev In order to transact, a default rate limit configuration must be set for users
*/
function _instantiateUserRateLimits(
TransactionType transactionType,
address rwaToken,
bytes32 userID
) internal {
RateLimitConfig memory rlConfig = transactionType ==
TransactionType.SUBSCRIPTION
? defaultUserSubscriptionLimitConfigs[rwaToken]
: defaultUserRedemptionLimitConfigs[rwaToken];
if (rlConfig.limit == 0) revert DefaultUserRateLimitNotSet();
RateLimit storage userRl = transactionType == TransactionType.SUBSCRIPTION
? userSubscriptionLimits[rwaToken][userID]
: userRedemptionLimits[rwaToken][userID];
userRl.capacityUsed = 0;
userRl.lastUpdated = block.timestamp;
userRl.limit = rlConfig.limit;
userRl.window = rlConfig.window;
}
/**
* @notice Calculates the current capacity used and the available capacity based on the rate
* limit configuration and time elapsed
* @param _capacityUsed The total capacity used at the last update
* @param _lastUpdated The timestamp (in seconds) when the last update occurred
* @param _limit The maximum allowable amount within the specified window
* @param _window The time window (in seconds) for which the limit applies
* @return currentCapacityUsed The decayed amount of capacity used based on the elapsed time
* since `lastUpdated`. If the time since `lastUpdated` exceeds the
* window, it returns zero.
* @return availableCapacity The amount of capacity available for new activity. If the time
* since lastUpdated exceeds the window, it returns the full limit.
* @dev This function applies a linear decay model to compute how much of the 'capacityUsed'
* remains based on the time elapsed since the last update.
*/
function _calculateDecay(
uint256 _capacityUsed,
uint256 _lastUpdated,
uint256 _limit,
uint48 _window
)
internal
view
returns (uint256 currentCapacityUsed, uint256 availableCapacity)
{
uint256 timeSinceLastUpdate = block.timestamp - _lastUpdated;
if (timeSinceLastUpdate >= _window) {
return (0, _limit);
} else {
uint256 decay = (_limit * timeSinceLastUpdate) / _window;
currentCapacityUsed = _capacityUsed > decay ? _capacityUsed - decay : 0;
availableCapacity = _limit > currentCapacityUsed
? _limit - currentCapacityUsed
: 0;
return (currentCapacityUsed, availableCapacity);
}
}
/**
* @notice Returns the current global subscription limit for a given RWA token,
* factoring in the decay
* @param rwaToken The address of the RWA token
* @return currentCapacityUsed The current capacity used based on the decay model
* @return availableCapacity The available capacity for new subscriptions
*/
function getCurrentGlobalSubscriptionLimit(
address rwaToken
)
external
view
returns (uint256 currentCapacityUsed, uint256 availableCapacity)
{
RateLimit memory rl = globalSubscriptionLimits[rwaToken];
return
_calculateDecay(rl.capacityUsed, rl.lastUpdated, rl.limit, rl.window);
}
/**
* @notice Returns current global redemption limit for a given RWA token,
* factoring in the decay
* @param rwaToken The address of the RWA token
* @return currentCapacityUsed The current capacity used based on the decay model
* @return availableCapacity The available capacity for new redemptions
*/
function getCurrentGlobalRedemptionLimit(
address rwaToken
)
external
view
returns (uint256 currentCapacityUsed, uint256 availableCapacity)
{
RateLimit memory rl = globalRedemptionLimits[rwaToken];
return
_calculateDecay(rl.capacityUsed, rl.lastUpdated, rl.limit, rl.window);
}
/**
* @notice Returns the current subscription limit for a given user,
* factoring in the decay
* @param rwaToken The address of the RWA token
* @param userID The ID of the user
* @return currentCapacityUsed The current capacity used based on the decay model
* @return availableCapacity The available capacity for new subscriptions
*/
function getCurrentUserSubscriptionLimit(
address rwaToken,
bytes32 userID
)
external
view
returns (uint256 currentCapacityUsed, uint256 availableCapacity)
{
RateLimit memory rl = userSubscriptionLimits[rwaToken][userID];
return
_calculateDecay(rl.capacityUsed, rl.lastUpdated, rl.limit, rl.window);
}
/**
* @notice Returns the current redemption limit for a given user, factoring in
* the decay.
* @param rwaToken The address of the RWA token
* @param userID The ID of the user
* @return currentCapacityUsed The current capacity used based on the decay model
* @return availableCapacity The available capacity for new redemptions
*/
function getCurrentUserRedemptionLimit(
address rwaToken,
bytes32 userID
)
external
view
returns (uint256 currentCapacityUsed, uint256 availableCapacity)
{
RateLimit memory rl = userRedemptionLimits[rwaToken][userID];
return
_calculateDecay(rl.capacityUsed, rl.lastUpdated, rl.limit, rl.window);
}
/**
* @notice Sets the global subscription limit for a given RWA token.
* @param rwaToken The address of the RWA token.
* @param limit The maximum allowable amount (in USD) within the specified window.
* @param window The time window (in seconds) for which the limit applies.
*/
function setGlobalSubscriptionLimit(
address rwaToken,
uint256 limit,
uint48 window
) external onlyRole(CONFIGURER_ROLE) {
if (rwaToken == address(0)) revert RWAAddressCantBeZero();
globalSubscriptionLimits[rwaToken] = RateLimit({
capacityUsed: 0,
lastUpdated: block.timestamp,
limit: limit,
window: window
});
emit GlobalRateLimitSet(
TransactionType.SUBSCRIPTION,
rwaToken,
limit,
window
);
}
/**
* @notice Sets the global redemption limit for a given RWA token.
* @param rwaToken The address of the RWA token.
* @param limit The maximum allowable amount (in USD) within the specified window.
* @param window The time window (in seconds) for which the limit applies.
*/
function setGlobalRedemptionLimit(
address rwaToken,
uint256 limit,
uint48 window
) external onlyRole(CONFIGURER_ROLE) {
if (rwaToken == address(0)) revert RWAAddressCantBeZero();
globalRedemptionLimits[rwaToken] = RateLimit({
capacityUsed: 0,
lastUpdated: block.timestamp,
limit: limit,
window: window
});
emit GlobalRateLimitSet(
TransactionType.REDEMPTION,
rwaToken,
limit,
window
);
}
/**
* @notice Sets the default subscription limit configuration for a given RWA token.
* @param rwaToken The address of the RWA token.
* @param limit The maximum allowable amount (in USD) within the specified window.
* @param window The time window (in seconds) for which the limit applies.
*/
function setDefaultUserSubscriptionLimitConfig(
address rwaToken,
uint256 limit,
uint48 window
) external onlyRole(CONFIGURER_ROLE) {
if (rwaToken == address(0)) revert RWAAddressCantBeZero();
defaultUserSubscriptionLimitConfigs[rwaToken] = RateLimitConfig({
limit: limit,
window: window
});
emit DefaultUserRateLimitSet(
TransactionType.SUBSCRIPTION,
rwaToken,
limit,
window
);
}
/**
* @notice Sets the default redemption limit configuration for a given RWA token.
* @param rwaToken The address of the RWA token.
* @param limit The maximum allowable amount (in USD) within the specified window.
* @param window The time window (in seconds) for which the limit applies.
*/
function setDefaultUserRedemptionLimitConfig(
address rwaToken,
uint256 limit,
uint48 window
) external onlyRole(CONFIGURER_ROLE) {
if (rwaToken == address(0)) revert RWAAddressCantBeZero();
defaultUserRedemptionLimitConfigs[rwaToken] = RateLimitConfig({
limit: limit,
window: window
});
emit DefaultUserRateLimitSet(
TransactionType.REDEMPTION,
rwaToken,
limit,
window
);
}
/**
* @notice Sets the subscription rate limit for a specific user.
* @param rwaToken The address of the RWA token.
* @param userID The ID of the user.
* @param subscriptionLimit The maximum allowable amount (in USD) within the specified window.
* @param subscriptionWindow The time window (in seconds) for which the limit applies.
*/
function setUserSubscriptionRateLimit(
address rwaToken,
bytes32 userID,
uint256 subscriptionLimit,
uint48 subscriptionWindow
) external onlyRole(CONFIGURER_ROLE) {
if (rwaToken == address(0)) revert RWAAddressCantBeZero();
if (userID == 0) revert UserIDCantBeZero();
userSubscriptionLimits[rwaToken][userID] = RateLimit({
capacityUsed: 0,
lastUpdated: block.timestamp,
limit: subscriptionLimit,
window: subscriptionWindow
});
emit UserRateLimitSet(
TransactionType.SUBSCRIPTION,
rwaToken,
userID,
subscriptionLimit,
subscriptionWindow
);
}
/**
* @notice Sets the redemption rate limit for a specific user.
* @param rwaToken The address of the RWA token
* @param userID The ID of the user
* @param redemptionLimit The maximum allowable amount (in USD) within the specified window
* @param redemptionWindow The time window (in seconds) for which the limit applies
*/
function setUserRedemptionRateLimit(
address rwaToken,
bytes32 userID,
uint256 redemptionLimit,
uint48 redemptionWindow
) external onlyRole(CONFIGURER_ROLE) {
if (rwaToken == address(0)) revert RWAAddressCantBeZero();
if (userID == 0) revert UserIDCantBeZero();
userRedemptionLimits[rwaToken][userID] = RateLimit({
capacityUsed: 0,
lastUpdated: block.timestamp,
limit: redemptionLimit,
window: redemptionWindow
});
emit UserRateLimitSet(
TransactionType.REDEMPTION,
rwaToken,
userID,
redemptionLimit,
redemptionWindow
);
}
}// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IRegistrar {
function register(address token) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "contracts/external/openzeppelin/contracts/access/IAccessControlEnumerable.sol";
import "contracts/external/openzeppelin/contracts/access/AccessControl.sol";
import "contracts/external/openzeppelin/contracts/utils/EnumerableSet.sol";
/**
* @dev Extension of {AccessControl} that allows enumerating the members of each role.
*/
abstract contract AccessControlEnumerable is
IAccessControlEnumerable,
AccessControl
{
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return
interfaceId == type(IAccessControlEnumerable).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index)
public
view
virtual
override
returns (address)
{
return _roleMembers[role].at(index);
}
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role)
public
view
virtual
override
returns (uint256)
{
return _roleMembers[role].length();
}
/**
* @dev Overload {_grantRole} to track enumerable memberships
*/
function _grantRole(bytes32 role, address account) internal virtual override {
super._grantRole(role, account);
_roleMembers[role].add(account);
}
/**
* @dev Overload {_revokeRole} to track enumerable memberships
*/
function _revokeRole(bytes32 role, address account)
internal
virtual
override
{
super._revokeRole(role, account);
_roleMembers[role].remove(account);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
pragma solidity ^0.8.0;
import "contracts/external/openzeppelin/contracts/utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IIssuanceHours {
function checkIsValidHours() external;
}// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IOndoIDRegistry {
function getRegisteredID(
address rwaToken,
address user
) external view returns (bytes32 userID);
}// SPDX-License-Identifier: MIT
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IOndoRateLimiter {
enum TransactionType {
SUBSCRIPTION,
REDEMPTION
}
function checkAndUpdateRateLimit(
TransactionType transactionType,
address rwaToken,
bytes32 userID,
uint256 usdValue
) external;
}/**SPDX-License-Identifier: BUSL-1.1
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
// This interface is not inherited directly by RWA, instead, it is a
// subset of functions provided by all RWA tokens that the RWA Hub
// Client uses.
import "contracts/external/openzeppelin/contracts/token/IERC20.sol";
interface IRWALike is IERC20 {
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
function burnFrom(address from, uint256 amount) external;
}// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IGMTokenManagerErrors {
/// Error emitted when the token address is zero
error TokenAddressCantBeZero();
/// Error emitted when the deposit amount is too small
error DepositAmountTooSmall();
/// Error emitted when the user is not registered with the ID registry
error UserNotRegistered();
/// Error emitted when the redemption amount is too small
error RedemptionAmountTooSmall();
/// Error emitted when attempting to set the `OndoIDRegistry` address to zero
error IDRegistryAddressCantBeZero();
/// Error emitted when attempting to set the `OndoRateLimiter` address to zero
error RateLimiterAddressCantBeZero();
/// Error emitted when the minting functionality is paused
error GlobalMintsPaused();
/// Error emitted when the redemption functionality is paused
error GlobalRedemptionsPaused();
/// Error emitted when the minting functionality is paused for a specific token
error GMTokenMintsPaused();
/// Error emitted when the redemption functionality is paused for a specific token
error GMTokenRedemptionsPaused();
/// Error emitted attempting to set the `OndoSanityCheckOracle` address to zero
error SanityCheckOracleAddressCantBeZero();
/// Custom error for expired attestations
error AttestationExpired();
/// Custom error for attestion signed by unverifid signer
error InvalidAttestationSigner();
/// Custom error for invalid chain ID
error InvalidChainId();
/// Custom error for invalid quote direction
error InvalidQuoteSide();
/// Custom error for user ID mismatch
error UserIdMismatch(bytes32 expected, bytes32 actual);
/// Custom error for already redeemed attestations
error AttestationAlreadyExecuted();
/// Error emitted when attempting to set the `IssuanceHours` address to zero
error IssuanceHoursAddressCantBeZero();
// Error emitted when attempting to use an onUSD manager reliant function when the onUSD manager is set to zero
error OnUSDManagerNotEnabled();
/// Error emitted when the GM Token is not registered for minting/redemption
error GMTokenNotRegistered();
/// Error emitted when attempting to set the `onUSD` address to zero
error OnUSDAddressCantBeZero();
/// Error emitted when not enough onUSD was received from the user to perform the mint
error InsufficientOnUsdReceived();
}// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IGMTokenManagerEvents {
/**
* @notice Event emitted when an admin completes a mint for a recipient
* @param recipient The address of the recipient that receives the RWA tokens
* @param recipientId The user ID of the recipient
* @param rwaToken The address of the RWA token being minted
* @param rwaAmount The amount of RWA tokens minted in decimals of the RWA token
* @param metadata Additional metadata to associate with the mint
*/
event AdminMint(
address indexed recipient,
bytes32 indexed recipientId,
address indexed rwaToken,
uint256 rwaAmount,
bytes32 metadata
);
/**
* @notice Event emitted when the `OndoIDRegistry` contract is set
* @param oldOndoIDRegistry The old `OndoIDRegistry` contract address
* @param newOndoIDRegistry The new `OndoIDRegistry` contract address
*/
event OndoIDRegistrySet(
address indexed oldOndoIDRegistry,
address indexed newOndoIDRegistry
);
/**
* @notice Event emitted when the `OndoRateLimiter` contract is set
* @param oldOndoRateLimiter The old `OndoRateLimiter` contract address
* @param newOndoRateLimiter The new `OndoRateLimiter` contract address
*/
event OndoRateLimiterSet(
address indexed oldOndoRateLimiter,
address indexed newOndoRateLimiter
);
/**
* @notice Event emitted when subscription minimum is set
* @param oldMinDepositAmount Old subscription minimum
* @param newMinDepositAmount New subscription minimum
*/
event MinimumDepositAmountSet(
uint256 indexed oldMinDepositAmount,
uint256 indexed newMinDepositAmount
);
/**
* @notice Event emitted when redeem minimum is set
* @param oldMinRedemptionAmount Old redeem minimum
* @param newMinRedemptionAmount New redeem minimum
*/
event MinimumRedemptionAmountSet(
uint256 indexed oldMinRedemptionAmount,
uint256 indexed newMinRedemptionAmount
);
/**
* @notice Event emitted when the `OndoSanityCheckOracle` contract is set
* @param oldOndoSanityCheckOracle The old `OndoSanityCheckOracle` contract address
* @param newOndoSanityCheckOracle The new `OndoSanityCheckOracle` contract address
*/
event OndoSanityCheckOracleSet(
address indexed oldOndoSanityCheckOracle,
address indexed newOndoSanityCheckOracle
);
/**
* @notice Event emitted when the `IssuanceHours` contract is set
* @param oldIssuanceHours The old `IssuanceHours` contract address
* @param newIssuanceHours The new `IssuanceHours` contract address
*/
event IssuanceHoursSet(
address indexed oldIssuanceHours,
address indexed newIssuanceHours
);
/**
* @notice Event emitted when the `OnUSDManager` contract is set
* @param oldOnUSDManager The old `OnUSDManager` contract address
* @param newOnUSDManager The new `OnUSDManager` contract address
*/
event OnUSDManagerSet(
address indexed oldOnUSDManager,
address indexed newOnUSDManager
);
/**
* @notice Event emitted when the accepted GM token is set
* @param gmToken The address of the GM token
* @param registered Whether the GM token is registered
*/
event GMTokenRegistered(address indexed gmToken, bool indexed registered);
/// Event emitted when minting functionality is paused
event GlobalMintingPaused();
/// Event emitted when minting functionality is unpaused
event GlobalMintingUnpaused();
/// Event emitted when redeem functionality is paused
event GlobalRedeemingPaused();
/// Event emitted when redeem functionality is unpaused
event GlobalRedeemingUnpaused();
/**
* @notice Event emitted when minting is paused for a specific GM token
* @param gmToken The address of the GM token
*/
event GMTokenMintingPaused(address indexed gmToken);
/**
* @notice Event emitted when minting is unpaused for a specific GM token
* @param gmToken The address of the GM token
*/
event GMTokenMintingUnpaused(address indexed gmToken);
/**
* @notice Event emitted when redemption is paused for a specific GM token
* @param gmToken The address of the GM token
*/
event GMTokenRedeemingPaused(address indexed gmToken);
/**
* @notice Event emitted when redemption is unpaused for a specific GM token
* @param gmToken The address of the GM token
*/
event GMTokenRedeemingUnpaused(address indexed gmToken);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "contracts/external/openzeppelin/contracts/token/IERC20.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.0;
import "./ECDSA.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
* thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
* they need in their contracts using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* _Available since v3.4._
*/
abstract contract EIP712 {
/* solhint-disable var-name-mixedcase */
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
address private immutable _CACHED_THIS;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
/* solhint-enable var-name-mixedcase */
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes(version));
bytes32 typeHash = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_CACHED_THIS = address(this);
_TYPE_HASH = typeHash;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
function _buildDomainSeparator(
bytes32 typeHash,
bytes32 nameHash,
bytes32 versionHash
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}/**SPDX-License-Identifier: BUSL-1.1
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
/**
* @title IGMTokenManager
* @author Ondo Finance
*/
interface IGMTokenManager {
/**
* @notice Enum for the direction of the quote
* @param BUY Indicates that the user is buying GM tokens
* @param SELL Indicates that the user is selling GM tokens
*/
enum QuoteSide {
// Indicates that the user is buying GM tokens
BUY,
// Indicates that the user is selling GM tokens
SELL
}
/**
* @notice Quote struct that is signed by the attestation signer
* @param attestationId The ID of the quote
* @param chainId The chain ID of the quote is intended for
* @param userId The user ID the quote is intended for
* @param asset The address of the GM token being bought or sold
* @param price The price of the GM token in USD with 18 decimals
* @param quantity The quantity of GM tokens being bought or sold
* @param expiration The expiration of the quote in seconds since the epoch
* @param side The direction of the quote (BUY or SELL)
* @param additionalData Any additional data that is needed for the quote
*/
struct Quote {
uint256 chainId;
uint256 attestationId;
bytes32 userId;
address asset;
uint256 price;
uint256 quantity;
uint256 expiration;
QuoteSide side;
bytes32 additionalData;
}
/**
* @notice Event emitted when a trade is executed with an attestation
* @param executionId The monotonically increasing ID of the trade
* @param attestationId The ID of the quote
* @param chainId The chain ID the quote is intended to be used
* @param userId The user ID the quote is intended for
* @param side The direction of the quote (BUY or SELL)
* @param asset The address of the GM token being bought or sold
* @param price The price of the GM token in USD with 18 decimals
* @param quantity The quantity of GM tokens being bought or sold
* @param expiration The expiration of the quote in seconds since the epoch
* @param additionalData Any additional data that is needed for the quote
*/
event TradeExecuted(
uint256 executionId,
uint256 attestationId,
uint256 chainId,
bytes32 userId,
QuoteSide side,
address asset,
uint256 price,
uint256 quantity,
uint256 expiration,
bytes32 additionalData
);
function mintWithAttestation(
Quote calldata quote,
bytes memory signature,
address depositToken,
uint256 depositAmount
) external returns (uint256 receivedGmTokenAmount);
function redeemWithAttestation(
Quote calldata quote,
bytes memory signature,
address receiveToken,
uint256 minimumReceiveAmount
) external returns (uint256 receivedUsdoAmount);
}/**SPDX-License-Identifier: BUSL-1.1
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
/**
* @title IOndoSanityCheckOracle
* @author Ondo Finance
*/
interface IOndoSanityCheckOracle {
/**
* @notice Check if the price is valid
* @param token The address of the token
* @param price The price of the token
*/
function validatePrice(address token, uint256 price) external view;
}// SPDX-License-Identifier: BUSL-1.1
/*
▄▄█████████▄
╓██▀└ ,╓▄▄▄, '▀██▄
██▀ ▄██▀▀╙╙▀▀██▄ └██µ ,, ,, , ,,, ,,,
██ ,██¬ ▄████▄ ▀█▄ ╙█▄ ▄███▀▀███▄ ███▄ ██ ███▀▀▀███▄ ▄███▀▀███,
██ ██ ╒█▀' ╙█▌ ╙█▌ ██ ▐██ ███ █████, ██ ██▌ └██▌ ██▌ └██▌
██ ▐█▌ ██ ╟█ █▌ ╟█ ██▌ ▐██ ██ └███ ██ ██▌ ╟██ j██ ╟██
╟█ ██ ╙██ ▄█▀ ▐█▌ ██ ╙██ ██▌ ██ ╙████ ██▌ ▄██▀ ██▌ ,██▀
██ "██, ╙▀▀███████████⌐ ╙████████▀ ██ ╙██ ███████▀▀ ╙███████▀`
██▄ ╙▀██▄▄▄▄▄,,, ¬─ '─¬
╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
╙▀▀██████R⌐
*/
pragma solidity 0.8.16;
interface IonUSDManager {
function subscribe(
address depositToken,
uint256 depositAmount,
uint256 minimumRwaReceived
) external returns (uint256 rwaAmountOut);
function redeem(
uint256 rwaAmount,
address receivingToken,
uint256 minimumTokenReceived
) external returns (uint256 receiveTokenAmount);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
pragma solidity ^0.8.0;
import "contracts/external/openzeppelin/contracts/access/IAccessControl.sol";
/**
* @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
*/
interface IAccessControlEnumerable is IAccessControl {
/**
* @dev Returns one of the accounts that have `role`. `index` must be a
* value between 0 and {getRoleMemberCount}, non-inclusive.
*
* Role bearers are not sorted in any particular way, and their ordering may
* change at any point.
*
* WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
* you perform all queries on the same block. See the following
* https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
* for more information.
*/
function getRoleMember(bytes32 role, uint256 index)
external
view
returns (address);
/**
* @dev Returns the number of accounts that have `role`. Can be used
* together with {getRoleMember} to enumerate all bearers of a role.
*/
function getRoleMemberCount(bytes32 role) external view returns (uint256);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
import "contracts/external/openzeppelin/contracts/access/IAccessControl.sol";
import "contracts/external/openzeppelin/contracts/utils/Context.sol";
import "contracts/external/openzeppelin/contracts/utils/Strings.sol";
import "contracts/external/openzeppelin/contracts/utils/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return
interfaceId == type(IAccessControl).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account)
public
view
virtual
override
returns (bool)
{
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role)
public
view
virtual
override
returns (bytes32)
{
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account)
public
virtual
override
onlyRole(getRoleAdmin(role))
{
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account)
public
virtual
override
onlyRole(getRoleAdmin(role))
{
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(
account == _msgSender(),
"AccessControl: can only renounce roles for self"
);
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value)
private
view
returns (bool)
{
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value)
internal
returns (bool)
{
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value)
internal
view
returns (bool)
{
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index)
internal
view
returns (bytes32)
{
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set)
internal
view
returns (bytes32[] memory)
{
return _values(set._inner);
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value)
internal
returns (bool)
{
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value)
internal
view
returns (bool)
{
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index)
internal
view
returns (address)
{
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set)
internal
view
returns (address[] memory)
{
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value)
internal
view
returns (bool)
{
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index)
internal
view
returns (uint256)
{
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set)
internal
view
returns (uint256[] memory)
{
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly {
result := store
}
return result;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender)
external
view
returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length)
internal
pure
returns (string memory)
{
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(
bytes32 indexed role,
bytes32 indexed previousAdminRole,
bytes32 indexed newAdminRole
);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(
bytes32 indexed role,
address indexed account,
address indexed sender
);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(
bytes32 indexed role,
address indexed account,
address indexed sender
);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "contracts/external/openzeppelin/contracts/utils/IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool)
{
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}{
"remappings": [
"eth-gas-reporter/=node_modules/eth-gas-reporter/",
"forge-tests/=forge-tests/",
"lib/=lib/",
"@aragon/=node_modules/@aragon/",
"@ensdomains/=node_modules/@ensdomains/",
"@openzeppelin/=node_modules/@openzeppelin/",
"@uniswap/=node_modules/@uniswap/",
"forge-std/=lib/forge-std/src/",
"hardhat-deploy/=node_modules/hardhat-deploy/",
"hardhat/=node_modules/hardhat/",
"math/=node_modules/@aragon/os/contracts/lib/math/",
"misc/=node_modules/@aragon/os/contracts/lib/misc/",
"solidity-bytes-utils/=node_modules/solidity-bytes-utils/",
"standards/=node_modules/@aragon/os/contracts/lib/standards/",
"token/=node_modules/@aragon/os/contracts/lib/token/",
"truffle/=node_modules/truffle/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs"
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "london",
"viaIR": false,
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"guardian","type":"address"},{"internalType":"address","name":"_gmTokenManager","type":"address"},{"internalType":"address","name":"_ondoRateLimiter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"GMTokenManagerCantBeZero","type":"error"},{"inputs":[],"name":"RateLimiterCantBeZero","type":"error"},{"inputs":[],"name":"TokenAddressCantBeZero","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"subscriptionLimit","type":"uint256"},{"indexed":false,"internalType":"uint48","name":"subscriptionWindow","type":"uint48"},{"indexed":false,"internalType":"uint256","name":"redemptionLimit","type":"uint256"},{"indexed":false,"internalType":"uint48","name":"redemptionWindow","type":"uint48"}],"name":"DefaultUserLimitConfigsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldManager","type":"address"},{"indexed":true,"internalType":"address","name":"newManager","type":"address"}],"name":"GMTokenManagerSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"subscriptionLimit","type":"uint256"},{"indexed":false,"internalType":"uint48","name":"subscriptionWindow","type":"uint48"},{"indexed":false,"internalType":"uint256","name":"redemptionLimit","type":"uint256"},{"indexed":false,"internalType":"uint48","name":"redemptionWindow","type":"uint48"}],"name":"GlobalLimitConfigsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldRateLimiter","type":"address"},{"indexed":true,"internalType":"address","name":"newRateLimiter","type":"address"}],"name":"RateLimiterSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"CONFIGURER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_FACTORY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNPAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultUserLimits","outputs":[{"internalType":"uint256","name":"subscriptionLimit","type":"uint256"},{"internalType":"uint256","name":"redemptionLimit","type":"uint256"},{"internalType":"uint48","name":"subscriptionWindow","type":"uint48"},{"internalType":"uint48","name":"redemptionWindow","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"globalLimits","outputs":[{"internalType":"uint256","name":"subscriptionLimit","type":"uint256"},{"internalType":"uint256","name":"redemptionLimit","type":"uint256"},{"internalType":"uint48","name":"subscriptionWindow","type":"uint48"},{"internalType":"uint48","name":"redemptionWindow","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gmTokenManager","outputs":[{"internalType":"contract GMTokenManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ondoRateLimiter","outputs":[{"internalType":"contract OndoRateLimiter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"register","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"subscriptionLimit","type":"uint256"},{"internalType":"uint48","name":"subscriptionWindow","type":"uint48"},{"internalType":"uint256","name":"redemptionLimit","type":"uint256"},{"internalType":"uint48","name":"redemptionWindow","type":"uint48"}],"name":"setDefaultUserLimitConfigs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gmTokenManager","type":"address"}],"name":"setGMTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"subscriptionLimit","type":"uint256"},{"internalType":"uint48","name":"subscriptionWindow","type":"uint48"},{"internalType":"uint256","name":"redemptionLimit","type":"uint256"},{"internalType":"uint48","name":"redemptionWindow","type":"uint48"}],"name":"setGlobalLimitConfigs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rateLimiter","type":"address"}],"name":"setRateLimiter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"}]Contract Creation Code
60806040523480156200001157600080fd5b506040516200186f3803806200186f8339810160408190526200003491620002cb565b6002805460ff191690556001600160a01b038216620000665760405163925c276960e01b815260040160405180910390fd5b6001600160a01b0381166200008e5760405163026cc47760e41b815260040160405180910390fd5b600980546001600160a01b038085166001600160a01b031992831617909255600a805492841692909116919091179055620000cb60008462000158565b620000f77ff45c97b23e2beeefda80e1ce5cb3e234aa7b6888ad5cbabb75bfd654dd8e102d8462000158565b620001237f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8462000158565b6200014f7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a8462000158565b50505062000315565b6200016f82826200019b60201b62000c0b1760201c565b60008281526001602090815260409091206200019691839062000c8f6200023c821b17901c565b505050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff1662000238576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055620001f73390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45b5050565b600062000253836001600160a01b0384166200025c565b90505b92915050565b6000818152600183016020526040812054620002a55750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915562000256565b50600062000256565b80516001600160a01b0381168114620002c657600080fd5b919050565b600080600060608486031215620002e157600080fd5b620002ec84620002ae565b9250620002fc60208501620002ae565b91506200030c60408501620002ae565b90509250925092565b61154a80620003256000396000f3fe608060405234801561001057600080fd5b50600436106101735760003560e01c80635e46d9b2116100de578063abbb9f4c11610097578063d547741f11610071578063d547741f14610393578063e63ab1e9146103a6578063f018d7c0146103cd578063fb1bb9de146103e057600080fd5b8063abbb9f4c14610346578063c2e11e3f1461035b578063ca15c8731461038057600080fd5b80635e46d9b2146102d6578063811400fa146102fd5780638456cb59146103105780639010d07c1461031857806391d148541461032b578063a217fddf1461033e57600080fd5b80633f4ba83a116101305780633f4ba83a146102375780634420e4861461023f578063453e0ba11461025257806348822587146102655780635302fe3f146102b85780635c975abb146102cb57600080fd5b806301ffc9a714610178578063110dc1d1146101a0578063248a9ca3146101cb5780632a63ce50146101fc5780632f2ff15d1461021157806336568abe14610224575b600080fd5b61018b610186366004611227565b610407565b60405190151581526020015b60405180910390f35b6009546101b3906001600160a01b031681565b6040516001600160a01b039091168152602001610197565b6101ee6101d9366004611251565b60009081526020819052604090206001015490565b604051908152602001610197565b61020f61020a366004611285565b610432565b005b61020f61021f3660046112e2565b6104f1565b61020f6102323660046112e2565b61051c565b61020f61059f565b61020f61024d36600461130e565b6105d5565b61020f61026036600461130e565b610965565b60065460075460085461028a92919065ffffffffffff80821691600160301b90041684565b60408051948552602085019390935265ffffffffffff91821692840192909252166060820152608001610197565b61020f6102c6366004611285565b610a02565b60025460ff1661018b565b6101ee7f03ddcdb3d1fe1a4e14dc10db6bdb472b365c57f06455b8de89b9193741e2d92481565b600a546101b3906001600160a01b031681565b61020f610ab6565b6101b3610326366004611329565b610ae9565b61018b6103393660046112e2565b610b08565b6101ee600081565b6101ee6000805160206114f583398151915281565b60035460045460055461028a92919065ffffffffffff80821691600160301b90041684565b6101ee61038e366004611251565b610b31565b61020f6103a13660046112e2565b610b48565b6101ee7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b61020f6103db36600461130e565b610b6e565b6101ee7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a81565b60006001600160e01b03198216635a05180f60e01b148061042c575061042c82610ca4565b92915050565b6000805160206114f583398151915261044b8133610cd9565b6040805160808082018352878252602080830187905265ffffffffffff888116848601819052908716606094850181905260038b90556004899055600580546bffffffffffffffffffffffff19168317600160301b830217905585518b815292830191909152938101879052918201929092527f62b0c5e2394b4bd112bac902f5b90c68fd43313dab83a7ac5c6d8c2a08a2246a91015b60405180910390a15050505050565b60008281526020819052604090206001015461050d8133610cd9565b6105178383610d3d565b505050565b6001600160a01b03811633146105915760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b60648201526084015b60405180910390fd5b61059b8282610d5f565b5050565b7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a6105ca8133610cd9565b6105d2610d81565b50565b7f03ddcdb3d1fe1a4e14dc10db6bdb472b365c57f06455b8de89b9193741e2d9246106008133610cd9565b60025460ff16156106465760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610588565b6001600160a01b03821661066d5760405163885ce5f160e01b815260040160405180910390fd5b6009546040516312359a8160e11b81526001600160a01b038481166004830152600160248301529091169063246b350290604401600060405180830381600087803b1580156106bb57600080fd5b505af11580156106cf573d6000803e3d6000fd5b5050600954604051632f2ff15d60e01b81527f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a660048201526001600160a01b0391821660248201529085169250632f2ff15d9150604401600060405180830381600087803b15801561074057600080fd5b505af1158015610754573d6000803e3d6000fd5b5050600a546003546005546040516361d925cb60e01b81526001600160a01b0390931694506361d925cb93506107989287929165ffffffffffff169060040161134b565b600060405180830381600087803b1580156107b257600080fd5b505af11580156107c6573d6000803e3d6000fd5b5050600a546004805460055460405163ab9613dd60e01b81526001600160a01b03909416955063ab9613dd9450610810938893600160301b90920465ffffffffffff16910161134b565b600060405180830381600087803b15801561082a57600080fd5b505af115801561083e573d6000803e3d6000fd5b5050600a54600654600854604051638a75d46f60e01b81526001600160a01b039093169450638a75d46f93506108829287929165ffffffffffff169060040161134b565b600060405180830381600087803b15801561089c57600080fd5b505af11580156108b0573d6000803e3d6000fd5b5050600a54600754600854604051631989e49960e01b81526001600160a01b039093169450631989e49993506108fb92879291600160301b900465ffffffffffff169060040161134b565b600060405180830381600087803b15801561091557600080fd5b505af1158015610929573d6000803e3d6000fd5b50506040516001600160a01b03851692507f158412daecdc1456d01568828bcdb18464cc7f1ce0215ddbc3f3cfede9d1e63d9150600090a25050565b6000805160206114f583398151915261097e8133610cd9565b6001600160a01b0382166109a55760405163925c276960e01b815260040160405180910390fd5b6009546040516001600160a01b038085169216907f66de4cb9c7ec536a04f93ff32d9ad0099714db9f52364ed5eb6bdbba16a8466390600090a350600980546001600160a01b0319166001600160a01b0392909216919091179055565b6000805160206114f5833981519152610a1b8133610cd9565b6040805160808082018352878252602080830187905265ffffffffffff888116848601819052908716606094850181905260068b90556007899055600880546bffffffffffffffffffffffff19168317600160301b830217905585518b815292830191909152938101879052918201929092527fcee0f939ab0453aa2f7270335f70024ffe7b7061b1d1d407711038793d4961de91016104e2565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a610ae18133610cd9565b6105d2610e14565b6000828152600160205260408120610b019083610e8f565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b600081815260016020526040812061042c90610e9b565b600082815260208190526040902060010154610b648133610cd9565b6105178383610d5f565b6000805160206114f5833981519152610b878133610cd9565b6001600160a01b038216610bae5760405163026cc47760e41b815260040160405180910390fd5b600a546040516001600160a01b038085169216907ffaa9dce1c0ebae9689ef9f8e05fc399b20c834ce83b4cb0fa5c9f54d8b6f41a590600090a350600a80546001600160a01b0319166001600160a01b0392909216919091179055565b610c158282610b08565b61059b576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055610c4b3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610b01836001600160a01b038416610ea5565b60006001600160e01b03198216637965db0b60e01b148061042c57506301ffc9a760e01b6001600160e01b031983161461042c565b610ce38282610b08565b61059b57610cfb816001600160a01b03166014610ef4565b610d06836020610ef4565b604051602001610d17929190611398565b60408051601f198184030181529082905262461bcd60e51b82526105889160040161140d565b610d478282610c0b565b60008281526001602052604090206105179082610c8f565b610d698282611090565b600082815260016020526040902061051790826110f5565b60025460ff16610dca5760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610588565b6002805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b60025460ff1615610e5a5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610588565b6002805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610df73390565b6000610b01838361110a565b600061042c825490565b6000818152600183016020526040812054610eec5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561042c565b50600061042c565b60606000610f03836002611456565b610f0e906002611475565b67ffffffffffffffff811115610f2657610f26611488565b6040519080825280601f01601f191660200182016040528015610f50576020820181803683370190505b509050600360fc1b81600081518110610f6b57610f6b61149e565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110610f9a57610f9a61149e565b60200101906001600160f81b031916908160001a9053506000610fbe846002611456565b610fc9906001611475565b90505b6001811115611041576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110610ffd57610ffd61149e565b1a60f81b8282815181106110135761101361149e565b60200101906001600160f81b031916908160001a90535060049490941c9361103a816114b4565b9050610fcc565b508315610b015760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610588565b61109a8282610b08565b1561059b576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610b01836001600160a01b038416611134565b60008260000182815481106111215761112161149e565b9060005260206000200154905092915050565b6000818152600183016020526040812054801561121d5760006111586001836114cb565b855490915060009061116c906001906114cb565b90508181146111d157600086600001828154811061118c5761118c61149e565b90600052602060002001549050808760000184815481106111af576111af61149e565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806111e2576111e26114de565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061042c565b600091505061042c565b60006020828403121561123957600080fd5b81356001600160e01b031981168114610b0157600080fd5b60006020828403121561126357600080fd5b5035919050565b803565ffffffffffff8116811461128057600080fd5b919050565b6000806000806080858703121561129b57600080fd5b843593506112ab6020860161126a565b9250604085013591506112c06060860161126a565b905092959194509250565b80356001600160a01b038116811461128057600080fd5b600080604083850312156112f557600080fd5b82359150611305602084016112cb565b90509250929050565b60006020828403121561132057600080fd5b610b01826112cb565b6000806040838503121561133c57600080fd5b50508035926020909101359150565b6001600160a01b03939093168352602083019190915265ffffffffffff16604082015260600190565b60005b8381101561138f578181015183820152602001611377565b50506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516113d0816017850160208801611374565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351611401816028840160208801611374565b01602801949350505050565b602081526000825180602084015261142c816040850160208701611374565b601f01601f19169190910160400192915050565b634e487b7160e01b600052601160045260246000fd5b600081600019048311821515161561147057611470611440565b500290565b8082018082111561042c5761042c611440565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000816114c3576114c3611440565b506000190190565b8181038181111561042c5761042c611440565b634e487b7160e01b600052603160045260246000fdfef45c97b23e2beeefda80e1ce5cb3e234aa7b6888ad5cbabb75bfd654dd8e102da26469706673582212201ba81693e74373a22173541a529d006b3618ed707cd35c288096dc6be904952864736f6c63430008100033000000000000000000000000680fc6eb31a99704360a30418df6a4eed92506ff000000000000000000000000f09ec70775ca2c99d8f407ffdf331a69b14e96f60000000000000000000000000cacba2adfb6a93470658b1d82a727941c93df5e
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106101735760003560e01c80635e46d9b2116100de578063abbb9f4c11610097578063d547741f11610071578063d547741f14610393578063e63ab1e9146103a6578063f018d7c0146103cd578063fb1bb9de146103e057600080fd5b8063abbb9f4c14610346578063c2e11e3f1461035b578063ca15c8731461038057600080fd5b80635e46d9b2146102d6578063811400fa146102fd5780638456cb59146103105780639010d07c1461031857806391d148541461032b578063a217fddf1461033e57600080fd5b80633f4ba83a116101305780633f4ba83a146102375780634420e4861461023f578063453e0ba11461025257806348822587146102655780635302fe3f146102b85780635c975abb146102cb57600080fd5b806301ffc9a714610178578063110dc1d1146101a0578063248a9ca3146101cb5780632a63ce50146101fc5780632f2ff15d1461021157806336568abe14610224575b600080fd5b61018b610186366004611227565b610407565b60405190151581526020015b60405180910390f35b6009546101b3906001600160a01b031681565b6040516001600160a01b039091168152602001610197565b6101ee6101d9366004611251565b60009081526020819052604090206001015490565b604051908152602001610197565b61020f61020a366004611285565b610432565b005b61020f61021f3660046112e2565b6104f1565b61020f6102323660046112e2565b61051c565b61020f61059f565b61020f61024d36600461130e565b6105d5565b61020f61026036600461130e565b610965565b60065460075460085461028a92919065ffffffffffff80821691600160301b90041684565b60408051948552602085019390935265ffffffffffff91821692840192909252166060820152608001610197565b61020f6102c6366004611285565b610a02565b60025460ff1661018b565b6101ee7f03ddcdb3d1fe1a4e14dc10db6bdb472b365c57f06455b8de89b9193741e2d92481565b600a546101b3906001600160a01b031681565b61020f610ab6565b6101b3610326366004611329565b610ae9565b61018b6103393660046112e2565b610b08565b6101ee600081565b6101ee6000805160206114f583398151915281565b60035460045460055461028a92919065ffffffffffff80821691600160301b90041684565b6101ee61038e366004611251565b610b31565b61020f6103a13660046112e2565b610b48565b6101ee7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b61020f6103db36600461130e565b610b6e565b6101ee7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a81565b60006001600160e01b03198216635a05180f60e01b148061042c575061042c82610ca4565b92915050565b6000805160206114f583398151915261044b8133610cd9565b6040805160808082018352878252602080830187905265ffffffffffff888116848601819052908716606094850181905260038b90556004899055600580546bffffffffffffffffffffffff19168317600160301b830217905585518b815292830191909152938101879052918201929092527f62b0c5e2394b4bd112bac902f5b90c68fd43313dab83a7ac5c6d8c2a08a2246a91015b60405180910390a15050505050565b60008281526020819052604090206001015461050d8133610cd9565b6105178383610d3d565b505050565b6001600160a01b03811633146105915760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b60648201526084015b60405180910390fd5b61059b8282610d5f565b5050565b7f427da25fe773164f88948d3e215c94b6554e2ed5e5f203a821c9f2f6131cf75a6105ca8133610cd9565b6105d2610d81565b50565b7f03ddcdb3d1fe1a4e14dc10db6bdb472b365c57f06455b8de89b9193741e2d9246106008133610cd9565b60025460ff16156106465760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610588565b6001600160a01b03821661066d5760405163885ce5f160e01b815260040160405180910390fd5b6009546040516312359a8160e11b81526001600160a01b038481166004830152600160248301529091169063246b350290604401600060405180830381600087803b1580156106bb57600080fd5b505af11580156106cf573d6000803e3d6000fd5b5050600954604051632f2ff15d60e01b81527f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a660048201526001600160a01b0391821660248201529085169250632f2ff15d9150604401600060405180830381600087803b15801561074057600080fd5b505af1158015610754573d6000803e3d6000fd5b5050600a546003546005546040516361d925cb60e01b81526001600160a01b0390931694506361d925cb93506107989287929165ffffffffffff169060040161134b565b600060405180830381600087803b1580156107b257600080fd5b505af11580156107c6573d6000803e3d6000fd5b5050600a546004805460055460405163ab9613dd60e01b81526001600160a01b03909416955063ab9613dd9450610810938893600160301b90920465ffffffffffff16910161134b565b600060405180830381600087803b15801561082a57600080fd5b505af115801561083e573d6000803e3d6000fd5b5050600a54600654600854604051638a75d46f60e01b81526001600160a01b039093169450638a75d46f93506108829287929165ffffffffffff169060040161134b565b600060405180830381600087803b15801561089c57600080fd5b505af11580156108b0573d6000803e3d6000fd5b5050600a54600754600854604051631989e49960e01b81526001600160a01b039093169450631989e49993506108fb92879291600160301b900465ffffffffffff169060040161134b565b600060405180830381600087803b15801561091557600080fd5b505af1158015610929573d6000803e3d6000fd5b50506040516001600160a01b03851692507f158412daecdc1456d01568828bcdb18464cc7f1ce0215ddbc3f3cfede9d1e63d9150600090a25050565b6000805160206114f583398151915261097e8133610cd9565b6001600160a01b0382166109a55760405163925c276960e01b815260040160405180910390fd5b6009546040516001600160a01b038085169216907f66de4cb9c7ec536a04f93ff32d9ad0099714db9f52364ed5eb6bdbba16a8466390600090a350600980546001600160a01b0319166001600160a01b0392909216919091179055565b6000805160206114f5833981519152610a1b8133610cd9565b6040805160808082018352878252602080830187905265ffffffffffff888116848601819052908716606094850181905260068b90556007899055600880546bffffffffffffffffffffffff19168317600160301b830217905585518b815292830191909152938101879052918201929092527fcee0f939ab0453aa2f7270335f70024ffe7b7061b1d1d407711038793d4961de91016104e2565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a610ae18133610cd9565b6105d2610e14565b6000828152600160205260408120610b019083610e8f565b9392505050565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b600081815260016020526040812061042c90610e9b565b600082815260208190526040902060010154610b648133610cd9565b6105178383610d5f565b6000805160206114f5833981519152610b878133610cd9565b6001600160a01b038216610bae5760405163026cc47760e41b815260040160405180910390fd5b600a546040516001600160a01b038085169216907ffaa9dce1c0ebae9689ef9f8e05fc399b20c834ce83b4cb0fa5c9f54d8b6f41a590600090a350600a80546001600160a01b0319166001600160a01b0392909216919091179055565b610c158282610b08565b61059b576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055610c4b3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610b01836001600160a01b038416610ea5565b60006001600160e01b03198216637965db0b60e01b148061042c57506301ffc9a760e01b6001600160e01b031983161461042c565b610ce38282610b08565b61059b57610cfb816001600160a01b03166014610ef4565b610d06836020610ef4565b604051602001610d17929190611398565b60408051601f198184030181529082905262461bcd60e51b82526105889160040161140d565b610d478282610c0b565b60008281526001602052604090206105179082610c8f565b610d698282611090565b600082815260016020526040902061051790826110f5565b60025460ff16610dca5760405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606401610588565b6002805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b60025460ff1615610e5a5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610588565b6002805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258610df73390565b6000610b01838361110a565b600061042c825490565b6000818152600183016020526040812054610eec5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561042c565b50600061042c565b60606000610f03836002611456565b610f0e906002611475565b67ffffffffffffffff811115610f2657610f26611488565b6040519080825280601f01601f191660200182016040528015610f50576020820181803683370190505b509050600360fc1b81600081518110610f6b57610f6b61149e565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110610f9a57610f9a61149e565b60200101906001600160f81b031916908160001a9053506000610fbe846002611456565b610fc9906001611475565b90505b6001811115611041576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110610ffd57610ffd61149e565b1a60f81b8282815181106110135761101361149e565b60200101906001600160f81b031916908160001a90535060049490941c9361103a816114b4565b9050610fcc565b508315610b015760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610588565b61109a8282610b08565b1561059b576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000610b01836001600160a01b038416611134565b60008260000182815481106111215761112161149e565b9060005260206000200154905092915050565b6000818152600183016020526040812054801561121d5760006111586001836114cb565b855490915060009061116c906001906114cb565b90508181146111d157600086600001828154811061118c5761118c61149e565b90600052602060002001549050808760000184815481106111af576111af61149e565b6000918252602080832090910192909255918252600188019052604090208390555b85548690806111e2576111e26114de565b60019003818190600052602060002001600090559055856001016000868152602001908152602001600020600090556001935050505061042c565b600091505061042c565b60006020828403121561123957600080fd5b81356001600160e01b031981168114610b0157600080fd5b60006020828403121561126357600080fd5b5035919050565b803565ffffffffffff8116811461128057600080fd5b919050565b6000806000806080858703121561129b57600080fd5b843593506112ab6020860161126a565b9250604085013591506112c06060860161126a565b905092959194509250565b80356001600160a01b038116811461128057600080fd5b600080604083850312156112f557600080fd5b82359150611305602084016112cb565b90509250929050565b60006020828403121561132057600080fd5b610b01826112cb565b6000806040838503121561133c57600080fd5b50508035926020909101359150565b6001600160a01b03939093168352602083019190915265ffffffffffff16604082015260600190565b60005b8381101561138f578181015183820152602001611377565b50506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516113d0816017850160208801611374565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351611401816028840160208801611374565b01602801949350505050565b602081526000825180602084015261142c816040850160208701611374565b601f01601f19169190910160400192915050565b634e487b7160e01b600052601160045260246000fd5b600081600019048311821515161561147057611470611440565b500290565b8082018082111561042c5761042c611440565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000816114c3576114c3611440565b506000190190565b8181038181111561042c5761042c611440565b634e487b7160e01b600052603160045260246000fdfef45c97b23e2beeefda80e1ce5cb3e234aa7b6888ad5cbabb75bfd654dd8e102da26469706673582212201ba81693e74373a22173541a529d006b3618ed707cd35c288096dc6be904952864736f6c63430008100033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000680fc6eb31a99704360a30418df6a4eed92506ff000000000000000000000000f09ec70775ca2c99d8f407ffdf331a69b14e96f60000000000000000000000000cacba2adfb6a93470658b1d82a727941c93df5e
-----Decoded View---------------
Arg [0] : guardian (address): 0x680fC6eB31A99704360A30418Df6a4eED92506FF
Arg [1] : _gmTokenManager (address): 0xf09ec70775cA2c99D8F407fFdF331a69b14e96F6
Arg [2] : _ondoRateLimiter (address): 0x0CacBA2aDFb6A93470658B1D82A727941C93DF5e
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000680fc6eb31a99704360a30418df6a4eed92506ff
Arg [1] : 000000000000000000000000f09ec70775ca2c99d8f407ffdf331a69b14e96f6
Arg [2] : 0000000000000000000000000cacba2adfb6a93470658b1d82a727941c93df5e
Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.