Description:
Smart contract deployed on Ethereum with Factory features.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/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:
*
* ```solidity
* 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}:
*
* ```solidity
* 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. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @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 returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @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 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.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual 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.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual 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 `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @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 Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
"
},
"@openzeppelin/contracts/access/IAccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/IAccessControl.sol)
pragma solidity ^0.8.20;
/**
* @dev External interface of AccessControl declared to support ERC-165 detection.
*/
interface IAccessControl {
/**
* @dev The `account` is missing a role.
*/
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/
error AccessControlBadConfirmation();
/**
* @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 to signal this.
*/
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. This account bears the admin role (for the granted role).
* Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
*/
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 `callerConfirmation`.
*/
function renounceRole(bytes32 role, address callerConfirmation) external;
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"@openzeppelin/contracts/utils/introspection/ERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC-165 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);
* }
* ```
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
"
},
"@openzeppelin/contracts/utils/introspection/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[ERC].
*
* 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[ERC 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);
}
"
},
"contracts/GradientRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title GradientRegistry
* @notice Central registry for storing and managing Gradient protocol contract addresses
* @dev This contract uses role-based access control and timelock to reduce centralization risk
*/
contract GradientRegistry is AccessControl {
// Role definitions
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
bytes32 public constant TIMELOCK_ROLE = keccak256("TIMELOCK_ROLE");
// Contract addresses
address public marketMakerFactory;
address public gradientToken;
address public orderbook;
address public fallbackExecutor;
address public router; // Uniswap V2 Router address
bool public isContractsInitialized;
// Timelock configuration
uint256 public timelockDuration = 24 hours;
mapping(bytes32 => uint256) public pendingChanges;
mapping(bytes32 => bool) public executedChanges;
mapping(string => bool) public isInitialized; // Track if each contract has been initialized
// Mapping for blocked tokens
mapping(address => bool) public blockedTokens;
mapping(address => bool) public isRewardDistributor;
// Mapping for authorized fulfillers
mapping(address => bool) public authorizedFulfillers;
// Events
event ContractAddressUpdated(
string indexed contractName,
address indexed oldAddress,
address indexed newAddress
);
event RewardDistributorSet(address indexed rewardDistributor);
event RewardDistributorRemoved(address indexed rewardDistributor);
event FulfillerAuthorized(address indexed fulfiller, bool status);
event RegistryChangeScheduled(
string indexed contractName,
address indexed newAddress,
bytes32 indexed changeId,
uint256 executionTime
);
event RegistryChangeExecuted(bytes32 indexed changeId);
event RegistryChangeCancelled(bytes32 indexed changeId);
event TimelockDurationUpdated(uint256 oldDuration, uint256 newDuration);
constructor() {
// Set up roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(OPERATOR_ROLE, msg.sender);
_grantRole(TIMELOCK_ROLE, msg.sender);
}
/**
* @notice Schedule a registry change with timelock
* @param contractName Name of the contract to update
* @param newAddress New address for the contract
* @dev Only callable by ADMIN_ROLE, requires timelock delay
*/
function scheduleContractAddressChange(
string calldata contractName,
address newAddress
) external onlyRole(ADMIN_ROLE) {
require(newAddress != address(0), "Invalid address");
// If this is the first time setting this contract, allow immediate execution
if (!isInitialized[contractName]) {
_executeContractAddressChange(contractName, newAddress);
isInitialized[contractName] = true;
return;
}
// For subsequent changes, require timelock
bytes32 changeId = keccak256(
abi.encodePacked(contractName, newAddress, block.timestamp)
);
require(pendingChanges[changeId] == 0, "Change already scheduled");
uint256 executionTime = block.timestamp + timelockDuration;
pendingChanges[changeId] = executionTime;
emit RegistryChangeScheduled(
contractName,
newAddress,
changeId,
executionTime
);
}
/**
* @notice Execute a scheduled registry change after timelock expires
* @param contractName Name of the contract to update
* @param newAddress New address for the contract
* @param timestamp Timestamp when the change was scheduled
* @dev Only callable by TIMELOCK_ROLE after delay expires
*/
function executeContractAddressChange(
string calldata contractName,
address newAddress,
uint256 timestamp
) external onlyRole(TIMELOCK_ROLE) {
bytes32 changeId = keccak256(
abi.encodePacked(contractName, newAddress, timestamp)
);
require(pendingChanges[changeId] > 0, "No pending change found");
require(!executedChanges[changeId], "Change already executed");
require(
block.timestamp >= pendingChanges[changeId],
"Timelock not expired"
);
require(
block.timestamp <= pendingChanges[changeId] + 24 hours,
"Execution window expired"
);
executedChanges[changeId] = true;
delete pendingChanges[changeId];
_executeContractAddressChange(contractName, newAddress);
emit RegistryChangeExecuted(changeId);
}
/**
* @notice Internal function to execute contract address changes
* @param contractName Name of the contract to update
* @param newAddress New address for the contract
*/
function _executeContractAddressChange(
string memory contractName,
address newAddress
) internal {
bytes32 nameHash = keccak256(bytes(contractName));
address oldAddress;
if (nameHash == keccak256(bytes("MarketMakerFactory"))) {
oldAddress = marketMakerFactory;
marketMakerFactory = newAddress;
} else if (nameHash == keccak256(bytes("GradientToken"))) {
oldAddress = gradientToken;
gradientToken = newAddress;
} else if (nameHash == keccak256(bytes("Orderbook"))) {
oldAddress = orderbook;
orderbook = newAddress;
} else if (nameHash == keccak256(bytes("FallbackExecutor"))) {
oldAddress = fallbackExecutor;
fallbackExecutor = newAddress;
} else if (nameHash == keccak256(bytes("Router"))) {
oldAddress = router;
router = newAddress;
} else {
revert("Invalid contract name");
}
emit ContractAddressUpdated(contractName, oldAddress, newAddress);
}
/**
* @notice Cancel a scheduled registry change (emergency only)
* @param contractName Name of the contract
* @param newAddress New address for the contract
* @param timestamp Timestamp when the change was scheduled
* @dev Only callable by ADMIN_ROLE
*/
function cancelContractAddressChange(
string calldata contractName,
address newAddress,
uint256 timestamp
) external onlyRole(ADMIN_ROLE) {
bytes32 changeId = keccak256(
abi.encodePacked(contractName, newAddress, timestamp)
);
require(pendingChanges[changeId] > 0, "No pending change found");
require(!executedChanges[changeId], "Change already executed");
delete pendingChanges[changeId];
emit RegistryChangeCancelled(changeId);
}
/**
* @notice Check if a scheduled change is ready for execution
* @param contractName Name of the contract
* @param newAddress New address for the contract
* @param timestamp Timestamp when the change was scheduled
* @return ready Whether the change is ready for execution
* @return executionTime When the change can be executed
*/
function isChangeReady(
string calldata contractName,
address newAddress,
uint256 timestamp
) external view returns (bool ready, uint256 executionTime) {
bytes32 changeId = keccak256(
abi.encodePacked(contractName, newAddress, timestamp)
);
executionTime = pendingChanges[changeId];
if (executionTime == 0 || executedChanges[changeId]) {
return (false, 0);
}
ready =
block.timestamp >= executionTime &&
block.timestamp <= executionTime + 24 hours;
}
/**
* @notice Update the timelock duration
* @param newDuration New timelock duration in seconds
* @dev Only callable by ADMIN_ROLE, requires minimum duration
*/
function updateTimelockDuration(
uint256 newDuration
) external onlyRole(ADMIN_ROLE) {
require(newDuration >= 2 hours, "Duration too short");
require(newDuration <= 7 days, "Duration too long");
uint256 oldDuration = timelockDuration;
timelockDuration = newDuration;
emit TimelockDurationUpdated(oldDuration, newDuration);
}
/**
* @notice Set the main contract addresses (legacy function, requires timelock)
* @param _marketMakerFactory Address of the MarketMakerFactory contract
* @param _gradientToken Address of the Gradient token contract
* @param _orderbook Address of the Orderbook contract
* @param _fallbackExecutor Address of the FallbackExecutor contract
* @param _router Address of the Uniswap V2 Router contract
* @dev This function now schedules changes instead of executing immediately
*/
function setMainContracts(
address _marketMakerFactory,
address _gradientToken,
address _orderbook,
address _fallbackExecutor,
address _router
) external onlyRole(ADMIN_ROLE) {
require(
_marketMakerFactory != address(0),
"Invalid market maker factory address"
);
require(_gradientToken != address(0), "Invalid gradient token address");
require(_orderbook != address(0), "Invalid orderbook address");
require(
_fallbackExecutor != address(0),
"Invalid fallback executor address"
);
require(_router != address(0), "Invalid router address");
if (!isContractsInitialized) {
_executeContractAddressChange(
"MarketMakerFactory",
_marketMakerFactory
);
isInitialized["MarketMakerFactory"] = true;
_executeContractAddressChange("GradientToken", _gradientToken);
isInitialized["GradientToken"] = true;
_executeContractAddressChange("Orderbook", _orderbook);
isInitialized["Orderbook"] = true;
_executeContractAddressChange(
"FallbackExecutor",
_fallbackExecutor
);
isInitialized["FallbackExecutor"] = true;
_executeContractAddressChange("Router", _router);
isInitialized["Router"] = true;
isContractsInitialized = true;
return;
}
// Set each contract address (first-time setup will be immediate)
this.scheduleContractAddressChange(
"MarketMakerFactory",
_marketMakerFactory
);
this.scheduleContractAddressChange("GradientToken", _gradientToken);
this.scheduleContractAddressChange("Orderbook", _orderbook);
this.scheduleContractAddressChange(
"FallbackExecutor",
_fallbackExecutor
);
this.scheduleContractAddressChange("Router", _router);
}
/**
* @notice Set the block status of a token
* @param token The address of the token to set the block status of
* @param blocked Whether the token should be blocked
*/
function setTokenBlockStatus(
address token,
bool blocked
) external onlyRole(OPERATOR_ROLE) {
blockedTokens[token] = blocked;
}
/**
* @notice Set a reward distributor address
* @param rewardDistributor The address of the reward distributor to authorize
* @dev Only callable by the contract owner
*/
function setRewardDistributor(
address rewardDistributor
) external onlyRole(ADMIN_ROLE) {
require(rewardDistributor != address(0), "Invalid distributor address");
isRewardDistributor[rewardDistributor] = true;
emit RewardDistributorSet(rewardDistributor);
}
function removeRewardDistributor(
address rewardDistributor
) external onlyRole(ADMIN_ROLE) {
require(rewardDistributor != address(0), "Invalid distributor address");
isRewardDistributor[rewardDistributor] = false;
emit RewardDistributorRemoved(rewardDistributor);
}
/**
* @notice Set an individual main contract address (first-time setup is immediate, subsequent changes require timelock)
* @param contractName Name of the contract to update
* @param newAddress New address for the contract
* @dev First-time setup is immediate, subsequent changes require timelock
*/
function setContractAddress(
string calldata contractName,
address newAddress
) external onlyRole(OPERATOR_ROLE) {
this.scheduleContractAddressChange(contractName, newAddress);
}
/**
* @notice Get all main contract addresses
* @return _marketMakerFactory Address of the MarketMakerFactory contract
* @return _gradientToken Address of the Gradient token contract
* @return _orderbook Address of the Orderbook contract
* @return _fallbackExecutor Address of the FallbackExecutor contract
* @return _router Address of the Uniswap V2 Router contract
*/
function getAllMainContracts()
external
view
returns (
address _marketMakerFactory,
address _gradientToken,
address _orderbook,
address _fallbackExecutor,
address _router
)
{
return (
marketMakerFactory,
gradientToken,
orderbook,
fallbackExecutor,
router
);
}
/**
* @notice Get the market maker factory contract address
* @return address The market maker factory contract address
*/
function getMarketMakerFactory() external view returns (address) {
return marketMakerFactory;
}
/**
* @notice Get the orderbook contract address
* @return address The orderbook contract address
*/
function getOrderbook() external view returns (address) {
return orderbook;
}
/**
* @notice Get the fallback executor contract address
* @return address The fallback executor contract address
*/
function getFallbackExecutor() external view returns (address) {
return fallbackExecutor;
}
/**
* @notice Get the router contract address
* @return address The router contract address
*/
function getRouter() external view returns (address) {
return router;
}
/**
* @notice Authorize or deauthorize a fulfiller
* @param fulfiller The address of the fulfiller to authorize
* @param status The status of the fulfiller
*/
function authorizeFulfiller(
address fulfiller,
bool status
) external onlyRole(ADMIN_ROLE) {
require(fulfiller != address(0), "Invalid fulfiller address");
authorizedFulfillers[fulfiller] = status;
emit FulfillerAuthorized(fulfiller, status);
}
/**
* @notice Check if an address is an authorized fulfiller
* @param fulfiller The address to check
* @return bool Whether the address is an authorized fulfiller
*/
function isAuthorizedFulfiller(
address fulfiller
) external view returns (bool) {
return authorizedFulfillers[fulfiller];
}
/**
* @notice Grant role to address
* @param role Role to grant
* @param account Account to grant role to
*/
function grantRole(
bytes32 role,
address account
) public override onlyRole(DEFAULT_ADMIN_ROLE) {
super.grantRole(role, account);
}
/**
* @notice Revoke role from address
* @param role Role to revoke
* @param account Account to revoke role from
*/
function revokeRole(
bytes32 role,
address account
) public override onlyRole(DEFAULT_ADMIN_ROLE) {
super.revokeRole(role, account);
}
/**
* @notice Renounce role
* @param role Role to renounce
* @param account Account to renounce role from
*/
function renounceRole(
bytes32 role,
address account
) public override onlyRole(DEFAULT_ADMIN_ROLE) {
super.renounceRole(role, account);
}
}
"
}
},
"settings": {
"viaIR": true,
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-09-24 17:41:12
Comments
Log in to comment.
No comments yet.