saving in place
This commit is contained in:
@@ -2,9 +2,11 @@
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
interface IUniswapV3Pool {
|
||||
function flash(
|
||||
@@ -56,8 +58,26 @@ interface ICamelotRouter {
|
||||
* @dev PRODUCTION-GRADE arbitrage executor for profitable MEV extraction
|
||||
* @notice This contract executes flash swap arbitrage between DEXes on Arbitrum
|
||||
*/
|
||||
contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable {
|
||||
contract ProductionArbitrageExecutor is ReentrancyGuard, AccessControl, Pausable {
|
||||
using SafeERC20 for IERC20;
|
||||
using Address for address;
|
||||
|
||||
// Role definitions for access control
|
||||
bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
|
||||
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
|
||||
bytes32 public constant EMERGENCY_ROLE = keccak256("EMERGENCY_ROLE");
|
||||
|
||||
// Factory address for pool validation
|
||||
address public constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
||||
|
||||
// Maximum slippage tolerance (5% = 500 basis points)
|
||||
uint256 public constant MAX_SLIPPAGE_BPS = 500;
|
||||
|
||||
// Mapping to track authorized pools for flash loans
|
||||
mapping(address => bool) public authorizedPools;
|
||||
|
||||
// Circuit breaker for emergency stops
|
||||
bool public emergencyStop = false;
|
||||
|
||||
// Router addresses on Arbitrum
|
||||
IUniswapV3Router public constant UNISWAP_V3_ROUTER =
|
||||
@@ -83,10 +103,14 @@ contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable {
|
||||
address indexed tokenB,
|
||||
uint256 amountIn,
|
||||
uint256 profit,
|
||||
uint256 gasUsed
|
||||
uint256 gasUsed,
|
||||
address indexed executor
|
||||
);
|
||||
|
||||
event ProfitWithdrawn(address indexed token, uint256 amount);
|
||||
|
||||
event ProfitWithdrawn(address indexed token, uint256 amount, address indexed recipient);
|
||||
event PoolAuthorized(address indexed pool, bool authorized);
|
||||
event EmergencyStopToggled(bool stopped);
|
||||
event SlippageExceeded(address indexed pool, uint256 expectedAmount, uint256 actualAmount);
|
||||
|
||||
struct ArbitrageParams {
|
||||
address tokenA;
|
||||
@@ -95,69 +119,186 @@ contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable {
|
||||
uint24 uniswapFee;
|
||||
address[] camelotPath;
|
||||
uint256 minProfit;
|
||||
uint256 maxSlippageBps; // Maximum allowed slippage in basis points
|
||||
bool buyOnUniswap; // true = buy on Uniswap, sell on Camelot
|
||||
uint256 deadline; // Transaction deadline for additional safety
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Constructor sets up roles and initial configuration
|
||||
*/
|
||||
constructor(address admin, address executor) {
|
||||
require(admin != address(0) && executor != address(0), "Invalid addresses");
|
||||
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
||||
_grantRole(ADMIN_ROLE, admin);
|
||||
_grantRole(EXECUTOR_ROLE, executor);
|
||||
_grantRole(EMERGENCY_ROLE, admin);
|
||||
|
||||
// Set role admin relationships
|
||||
_setRoleAdmin(EXECUTOR_ROLE, ADMIN_ROLE);
|
||||
_setRoleAdmin(EMERGENCY_ROLE, ADMIN_ROLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Authorize a pool for flash loans
|
||||
*/
|
||||
function authorizePool(address pool) external onlyRole(ADMIN_ROLE) {
|
||||
require(pool != address(0), "Invalid pool address");
|
||||
require(pool.isContract(), "Pool must be a contract");
|
||||
|
||||
authorizedPools[pool] = true;
|
||||
emit PoolAuthorized(pool, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Deauthorize a pool for flash loans
|
||||
*/
|
||||
function deauthorizePool(address pool) external onlyRole(ADMIN_ROLE) {
|
||||
authorizedPools[pool] = false;
|
||||
emit PoolAuthorized(pool, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency stop toggle
|
||||
*/
|
||||
function toggleEmergencyStop() external onlyRole(EMERGENCY_ROLE) {
|
||||
emergencyStop = !emergencyStop;
|
||||
emit EmergencyStopToggled(emergencyStop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Execute profitable arbitrage using flash swap
|
||||
* @dev Execute profitable arbitrage using flash swap with comprehensive security checks
|
||||
* @param pool Uniswap V3 pool to flash swap from
|
||||
* @param params Arbitrage parameters encoded as bytes
|
||||
*/
|
||||
function executeArbitrage(address pool, bytes calldata params) external onlyOwner {
|
||||
function executeArbitrage(address pool, bytes calldata params)
|
||||
external
|
||||
onlyRole(EXECUTOR_ROLE)
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
{
|
||||
require(!emergencyStop, "Emergency stop activated");
|
||||
require(authorizedPools[pool], "Pool not authorized");
|
||||
require(tx.gasprice <= maxGasPrice, "Gas price too high for profit");
|
||||
|
||||
require(pool.isContract(), "Invalid pool address");
|
||||
|
||||
ArbitrageParams memory arbParams = abi.decode(params, (ArbitrageParams));
|
||||
|
||||
|
||||
// Comprehensive parameter validation
|
||||
_validateArbitrageParams(arbParams);
|
||||
|
||||
// Validate minimum profit potential
|
||||
uint256 estimatedProfit = estimateProfit(arbParams);
|
||||
require(estimatedProfit >= minProfitThreshold, "Insufficient profit potential");
|
||||
|
||||
// Calculate optimal flash amount
|
||||
require(estimatedProfit >= arbParams.minProfit, "Below user-specified minimum profit");
|
||||
|
||||
// Ensure deadline hasn't passed
|
||||
require(block.timestamp <= arbParams.deadline, "Transaction deadline exceeded");
|
||||
|
||||
// Calculate optimal flash amount with safety checks
|
||||
uint256 flashAmount = calculateOptimalAmount(arbParams);
|
||||
|
||||
// Prepare flash swap data
|
||||
bytes memory flashData = abi.encode(arbParams, block.timestamp);
|
||||
|
||||
// Execute flash swap
|
||||
require(flashAmount > 0 && flashAmount <= arbParams.amountIn * 2, "Invalid flash amount");
|
||||
|
||||
// Prepare flash swap data with additional validation data
|
||||
bytes memory flashData = abi.encode(arbParams, block.timestamp, msg.sender);
|
||||
|
||||
// Execute flash swap with proper token validation
|
||||
require(_isValidPoolTokens(pool, arbParams.tokenA, arbParams.tokenB), "Invalid pool tokens");
|
||||
|
||||
if (arbParams.tokenA == IUniswapV3Pool(pool).token0()) {
|
||||
IUniswapV3Pool(pool).flash(address(this), flashAmount, 0, flashData);
|
||||
} else {
|
||||
IUniswapV3Pool(pool).flash(address(this), 0, flashAmount, flashData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validate arbitrage parameters comprehensively
|
||||
*/
|
||||
function _validateArbitrageParams(ArbitrageParams memory params) private pure {
|
||||
require(params.tokenA != address(0) && params.tokenB != address(0), "Invalid token addresses");
|
||||
require(params.tokenA != params.tokenB, "Tokens must be different");
|
||||
require(params.amountIn > 0, "Amount must be positive");
|
||||
require(params.minProfit > 0, "Minimum profit must be positive");
|
||||
require(params.maxSlippageBps <= MAX_SLIPPAGE_BPS, "Slippage tolerance too high");
|
||||
require(params.camelotPath.length >= 2, "Invalid Camelot path");
|
||||
require(params.deadline > 0, "Invalid deadline");
|
||||
require(params.uniswapFee == 500 || params.uniswapFee == 3000 || params.uniswapFee == 10000, "Invalid Uniswap fee");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validate pool tokens match arbitrage parameters
|
||||
*/
|
||||
function _isValidPoolTokens(address pool, address tokenA, address tokenB) private view returns (bool) {
|
||||
try IUniswapV3Pool(pool).token0() returns (address token0) {
|
||||
try IUniswapV3Pool(pool).token1() returns (address token1) {
|
||||
return (token0 == tokenA && token1 == tokenB) || (token0 == tokenB && token1 == tokenA);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Uniswap V3 flash callback - executes the arbitrage logic
|
||||
* @dev Uniswap V3 flash callback with enhanced security validation
|
||||
*/
|
||||
function uniswapV3FlashCallback(
|
||||
uint256 fee0,
|
||||
uint256 fee1,
|
||||
bytes calldata data
|
||||
) external {
|
||||
) external nonReentrant {
|
||||
uint256 gasStart = gasleft();
|
||||
|
||||
(ArbitrageParams memory params, uint256 deadline) = abi.decode(data, (ArbitrageParams, uint256));
|
||||
|
||||
// Validate callback is from legitimate pool
|
||||
require(isValidPool(msg.sender, params.tokenA, params.tokenB), "Invalid pool");
|
||||
require(block.timestamp <= deadline + 300, "Transaction too old");
|
||||
|
||||
uint256 amountOwed = params.tokenA == IUniswapV3Pool(msg.sender).token0() ?
|
||||
params.amountIn + fee0 : params.amountIn + fee1;
|
||||
|
||||
// Execute arbitrage strategy
|
||||
|
||||
// Critical: Validate callback is from authorized pool FIRST
|
||||
require(authorizedPools[msg.sender], "Unauthorized pool callback");
|
||||
require(!emergencyStop, "Emergency stop activated");
|
||||
|
||||
(ArbitrageParams memory params, uint256 timestamp, address originalCaller) =
|
||||
abi.decode(data, (ArbitrageParams, uint256, address));
|
||||
|
||||
// Enhanced callback validation
|
||||
require(_isValidPoolTokens(msg.sender, params.tokenA, params.tokenB), "Invalid pool tokens");
|
||||
require(block.timestamp <= timestamp + 300, "Callback too old");
|
||||
require(block.timestamp <= params.deadline, "Transaction deadline exceeded");
|
||||
|
||||
// Calculate amount owed with overflow protection
|
||||
uint256 flashFee = params.tokenA == IUniswapV3Pool(msg.sender).token0() ? fee0 : fee1;
|
||||
uint256 amountOwed;
|
||||
require(params.amountIn + flashFee >= params.amountIn, "Overflow in amount calculation");
|
||||
amountOwed = params.amountIn + flashFee;
|
||||
|
||||
// Ensure we have sufficient balance for flash loan execution
|
||||
uint256 initialBalance = IERC20(params.tokenA).balanceOf(address(this));
|
||||
require(initialBalance >= params.amountIn, "Insufficient flash loan balance");
|
||||
|
||||
// Execute arbitrage strategy with slippage protection
|
||||
uint256 profit = executeArbitrageStrategy(params, amountOwed);
|
||||
|
||||
// Ensure we made profit after repaying flash loan
|
||||
require(profit >= minProfitThreshold, "Arbitrage not profitable");
|
||||
|
||||
// Repay flash loan
|
||||
|
||||
// Enhanced profit validation
|
||||
require(profit >= minProfitThreshold, "Below minimum profit threshold");
|
||||
require(profit >= params.minProfit, "Below user-specified minimum");
|
||||
|
||||
// Ensure we can repay the flash loan
|
||||
uint256 finalBalance = IERC20(params.tokenA).balanceOf(address(this));
|
||||
require(finalBalance >= amountOwed, "Insufficient balance to repay flash loan");
|
||||
|
||||
// Repay flash loan with precise amount
|
||||
IERC20(params.tokenA).safeTransfer(msg.sender, amountOwed);
|
||||
|
||||
// Calculate gas cost in tokens
|
||||
|
||||
// Calculate actual gas cost
|
||||
uint256 gasUsed = gasStart - gasleft();
|
||||
|
||||
emit ArbitrageExecuted(params.tokenA, params.tokenB, params.amountIn, profit, gasUsed);
|
||||
|
||||
emit ArbitrageExecuted(
|
||||
params.tokenA,
|
||||
params.tokenB,
|
||||
params.amountIn,
|
||||
profit,
|
||||
gasUsed,
|
||||
originalCaller
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,33 +329,53 @@ contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Buy tokens on Uniswap V3
|
||||
* @dev Buy tokens on Uniswap V3 with precise approvals and slippage protection
|
||||
*/
|
||||
function buyOnUniswap(ArbitrageParams memory params) private returns (uint256 amountOut) {
|
||||
// Reset any existing approval to 0 first (for certain tokens like USDT)
|
||||
IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), 0);
|
||||
|
||||
// Approve exact amount needed, no more
|
||||
IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), params.amountIn);
|
||||
|
||||
|
||||
// Calculate minimum amount out with slippage protection
|
||||
uint256 minAmountOut = _calculateMinAmountOut(params.amountIn, params.maxSlippageBps);
|
||||
|
||||
IUniswapV3Router.ExactInputSingleParams memory swapParams = IUniswapV3Router.ExactInputSingleParams({
|
||||
tokenIn: params.tokenA,
|
||||
tokenOut: params.tokenB,
|
||||
fee: params.uniswapFee,
|
||||
recipient: address(this),
|
||||
deadline: block.timestamp + 300,
|
||||
deadline: params.deadline,
|
||||
amountIn: params.amountIn,
|
||||
amountOutMinimum: 0, // Will be calculated dynamically
|
||||
amountOutMinimum: minAmountOut,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
|
||||
|
||||
amountOut = UNISWAP_V3_ROUTER.exactInputSingle(swapParams);
|
||||
|
||||
// Reset approval after use for security
|
||||
IERC20(params.tokenA).safeApprove(address(UNISWAP_V3_ROUTER), 0);
|
||||
|
||||
// Validate slippage didn't exceed expectations
|
||||
require(amountOut >= minAmountOut, "Excessive slippage detected");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sell tokens on Uniswap V3
|
||||
* @dev Sell tokens on Uniswap V3 with precise approvals and slippage protection
|
||||
*/
|
||||
function sellOnUniswap(address tokenIn, address tokenOut, uint256 amountIn, uint24 fee)
|
||||
function sellOnUniswap(address tokenIn, address tokenOut, uint256 amountIn, uint24 fee)
|
||||
private returns (uint256 amountOut) {
|
||||
|
||||
|
||||
// Reset any existing approval to 0 first
|
||||
IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), 0);
|
||||
|
||||
// Approve exact amount needed
|
||||
IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), amountIn);
|
||||
|
||||
|
||||
// Calculate minimum amount out with slippage protection (assuming 0.5% slippage for simplicity)
|
||||
uint256 minAmountOut = (amountIn * 9950) / 10000; // 0.5% slippage
|
||||
|
||||
IUniswapV3Router.ExactInputSingleParams memory swapParams = IUniswapV3Router.ExactInputSingleParams({
|
||||
tokenIn: tokenIn,
|
||||
tokenOut: tokenOut,
|
||||
@@ -222,49 +383,78 @@ contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable {
|
||||
recipient: address(this),
|
||||
deadline: block.timestamp + 300,
|
||||
amountIn: amountIn,
|
||||
amountOutMinimum: 0,
|
||||
amountOutMinimum: minAmountOut,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
|
||||
|
||||
amountOut = UNISWAP_V3_ROUTER.exactInputSingle(swapParams);
|
||||
|
||||
// Reset approval after use
|
||||
IERC20(tokenIn).safeApprove(address(UNISWAP_V3_ROUTER), 0);
|
||||
|
||||
require(amountOut >= minAmountOut, "Excessive slippage on Uniswap sell");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Buy tokens on Camelot
|
||||
* @dev Buy tokens on Camelot with precise approvals and slippage protection
|
||||
*/
|
||||
function buyOnCamelot(ArbitrageParams memory params) private returns (uint256 amountOut) {
|
||||
// Reset approval to 0 first
|
||||
IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), 0);
|
||||
|
||||
// Approve exact amount needed
|
||||
IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), params.amountIn);
|
||||
|
||||
|
||||
// Calculate minimum amount out with slippage protection
|
||||
uint256 minAmountOut = _calculateMinAmountOut(params.amountIn, params.maxSlippageBps);
|
||||
|
||||
uint256[] memory amounts = CAMELOT_ROUTER.swapExactTokensForTokens(
|
||||
params.amountIn,
|
||||
0, // amountOutMin - calculated dynamically
|
||||
minAmountOut,
|
||||
params.camelotPath,
|
||||
address(this),
|
||||
address(0), // referrer
|
||||
block.timestamp + 300
|
||||
params.deadline
|
||||
);
|
||||
|
||||
|
||||
amountOut = amounts[amounts.length - 1];
|
||||
|
||||
// Reset approval after use
|
||||
IERC20(params.tokenA).safeApprove(address(CAMELOT_ROUTER), 0);
|
||||
|
||||
require(amountOut >= minAmountOut, "Excessive slippage on Camelot buy");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sell tokens on Camelot
|
||||
* @dev Sell tokens on Camelot with precise approvals and slippage protection
|
||||
*/
|
||||
function sellOnCamelot(address tokenIn, address tokenOut, uint256 amountIn, address[] memory path)
|
||||
function sellOnCamelot(address tokenIn, address tokenOut, uint256 amountIn, address[] memory path)
|
||||
private returns (uint256 amountOut) {
|
||||
|
||||
|
||||
// Reset approval to 0 first
|
||||
IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), 0);
|
||||
|
||||
// Approve exact amount needed
|
||||
IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), amountIn);
|
||||
|
||||
|
||||
// Calculate minimum amount out with slippage protection (0.5% slippage)
|
||||
uint256 minAmountOut = (amountIn * 9950) / 10000;
|
||||
|
||||
uint256[] memory amounts = CAMELOT_ROUTER.swapExactTokensForTokens(
|
||||
amountIn,
|
||||
0,
|
||||
minAmountOut,
|
||||
path,
|
||||
address(this),
|
||||
address(0),
|
||||
block.timestamp + 300
|
||||
);
|
||||
|
||||
|
||||
amountOut = amounts[amounts.length - 1];
|
||||
|
||||
// Reset approval after use
|
||||
IERC20(tokenIn).safeApprove(address(CAMELOT_ROUTER), 0);
|
||||
|
||||
require(amountOut >= minAmountOut, "Excessive slippage on Camelot sell");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,36 +523,88 @@ contract ProductionArbitrageExecutor is ReentrancyGuard, Ownable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Withdraw accumulated profits
|
||||
* @dev Calculate minimum amount out based on slippage tolerance
|
||||
*/
|
||||
function withdrawProfits(address token) external onlyOwner {
|
||||
function _calculateMinAmountOut(uint256 amountIn, uint256 slippageBps) private pure returns (uint256) {
|
||||
require(slippageBps <= MAX_SLIPPAGE_BPS, "Slippage too high");
|
||||
return (amountIn * (10000 - slippageBps)) / 10000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Withdraw accumulated profits with enhanced security
|
||||
*/
|
||||
function withdrawProfits(address token, uint256 amount)
|
||||
external
|
||||
onlyRole(ADMIN_ROLE)
|
||||
nonReentrant
|
||||
{
|
||||
require(token != address(0), "Invalid token address");
|
||||
require(amount > 0, "Amount must be positive");
|
||||
|
||||
uint256 balance = IERC20(token).balanceOf(address(this));
|
||||
require(balance > 0, "No profits to withdraw");
|
||||
|
||||
IERC20(token).safeTransfer(owner(), balance);
|
||||
emit ProfitWithdrawn(token, balance);
|
||||
require(balance >= amount, "Insufficient balance");
|
||||
require(amount <= balance, "Amount exceeds balance");
|
||||
|
||||
address admin = getRoleMember(ADMIN_ROLE, 0);
|
||||
IERC20(token).safeTransfer(admin, amount);
|
||||
|
||||
emit ProfitWithdrawn(token, amount, admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency withdrawal function with strict access control
|
||||
*/
|
||||
function emergencyWithdraw(address token, uint256 amount)
|
||||
external
|
||||
onlyRole(EMERGENCY_ROLE)
|
||||
nonReentrant
|
||||
{
|
||||
require(!emergencyStop, "Emergency stop active");
|
||||
require(token != address(0), "Invalid token");
|
||||
require(amount > 0, "Invalid amount");
|
||||
|
||||
uint256 balance = IERC20(token).balanceOf(address(this));
|
||||
require(balance >= amount, "Insufficient balance");
|
||||
|
||||
address emergencyAdmin = getRoleMember(EMERGENCY_ROLE, 0);
|
||||
IERC20(token).safeTransfer(emergencyAdmin, amount);
|
||||
|
||||
emit ProfitWithdrawn(token, amount, emergencyAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Emergency withdrawal function
|
||||
* @dev Update minimum profit threshold with validation
|
||||
*/
|
||||
function emergencyWithdraw(address token, uint256 amount) external onlyOwner {
|
||||
IERC20(token).safeTransfer(owner(), amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Update minimum profit threshold
|
||||
*/
|
||||
function setMinProfitThreshold(uint256 _minProfitThreshold) external onlyOwner {
|
||||
function setMinProfitThreshold(uint256 _minProfitThreshold) external onlyRole(ADMIN_ROLE) {
|
||||
require(_minProfitThreshold > 0, "Threshold must be positive");
|
||||
require(_minProfitThreshold <= 1 ether, "Threshold too high");
|
||||
|
||||
minProfitThreshold = _minProfitThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dev Update maximum gas price
|
||||
* @dev Update maximum gas price with validation
|
||||
*/
|
||||
function setMaxGasPrice(uint256 _maxGasPrice) external onlyOwner {
|
||||
function setMaxGasPrice(uint256 _maxGasPrice) external onlyRole(ADMIN_ROLE) {
|
||||
require(_maxGasPrice > 0, "Gas price must be positive");
|
||||
require(_maxGasPrice <= 50 gwei, "Gas price too high");
|
||||
|
||||
maxGasPrice = _maxGasPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Pause contract in case of emergency
|
||||
*/
|
||||
function pause() external onlyRole(EMERGENCY_ROLE) {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Unpause contract
|
||||
*/
|
||||
function unpause() external onlyRole(ADMIN_ROLE) {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Receive ETH
|
||||
|
||||
Reference in New Issue
Block a user