diff --git a/.github/workflows/change_batcher_gas_limit.yml b/.github/workflows/change_batcher_gas_limit.yml new file mode 100644 index 0000000..ccec82c --- /dev/null +++ b/.github/workflows/change_batcher_gas_limit.yml @@ -0,0 +1,130 @@ +name: Change Batcher Contract Transfer Gas Limit +on: + workflow_dispatch: + inputs: + chain: + description: 'Chain network (e.g., tbsc, bsc, eth, etc.)' + required: true + type: string + batcherContractAddress: + description: 'Batcher contract address' + required: true + type: string + newGasLimit: + description: 'New gas limit for transfers' + required: true + type: string + +jobs: + lint-and-test: + environment: dev + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + cache: 'npm' + - run: npm install + - run: npm run lint + - run: npm run test + env: + MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }} + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT }} + QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }} + QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }} + QUICKNODE_ARBITRUM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_SEPOLIA_API_KEY }} + QUICKNODE_ARBITRUM_ONE_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_ONE_API_KEY }} + QUICKNODE_OPTIMISM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_SEPOLIA_API_KEY }} + QUICKNODE_OPTIMISM_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_API_KEY }} + QUICKNODE_ZKSYNC_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ZKSYNC_SEPOLIA_API_KEY }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ALCHEMY_POLYGON_API_KEY: ${{ secrets.ALCHEMY_POLYGON_API_KEY }} + POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }} + BSCSCAN_API_KEY: ${{ secrets.BSCSCAN_API_KEY }} + ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }} + OPTIMISTIC_ETHERSCAN_API_KEY: ${{ secrets.OPTIMISTIC_ETHERSCAN_API_KEY }} + ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }} + BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} + CARTIO_BERA_EXPLORER_API_KEY: ${{ secrets.CARTIO_BERA_EXPLORER_API_KEY }} + BERA_EXPLORER_API_KEY: ${{ secrets.BERA_EXPLORER_API_KEY }} + OAS_EXPLORER_API_KEY: ${{ secrets.OAS_EXPLORER_API_KEY }} + CORE_DAO_TESTNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_TESTNET_EXPLORER_API_KEY }} + CORE_DAO_MAINNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_MAINNET_EXPLORER_API_KEY }} + FLARE_EXPLORER_API_KEY: ${{ secrets.FLARE_EXPLORER_API_KEY }} + SONGBIRD_EXPLORER_API_KEY: ${{ secrets.SONGBIRD_EXPLORER_API_KEY }} + XDC_EXPLORER_API_KEY: ${{ secrets.XDC_EXPLORER_API_KEY }} + WEMIX_EXPLORER_API_KEY: ${{ secrets.WEMIX_EXPLORER_API_KEY }} + BERA_RPC_URL: ${{ secrets.BERA_RPC_URL }} + MONAD_EXPLORER_API_KEY: ${{ secrets.MONAD_EXPLORER_API_KEY }} + SOMNIA_EXPLORER_API_KEY: ${{ secrets.SOMNIA_EXPLORER_API_KEY }} + SONEIUM_EXPLORER_API_KEY: ${{ secrets.SONEIUM_EXPLORER_API_KEY }} + WORLD_EXPLORER_API_KEY: ${{ secrets.WORLD_EXPLORER_API_KEY }} + CTC_EXPLORER_API_KEY: ${{ secrets.CTC_EXPLORER_API_KEY }} + PHAROS_EXPLORER_API_KEY: ${{ secrets.PHAROS_EXPLORER_API_KEY }} + HYPEEVM_EXPLORER_API_KEY: ${{ secrets.HYPEEVM_EXPLORER_API_KEY }} + SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }} + KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }} + IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }} + + change-batcher-gas-limit: + runs-on: ubuntu-latest + needs: [lint-and-test] + environment: ${{ contains(github.event.inputs.chain, 't') && 'testnet' || 'mainnet' }} + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + cache: 'npm' + - run: npm install + - run: npm run change-batcher-gas-limit --network ${{ github.event.inputs.chain }} + env: + BATCHER_CONTRACT_ADDRESS: ${{ github.event.inputs.batcherContractAddress }} + NEW_GAS_LIMIT: ${{ github.event.inputs.newGasLimit }} + MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT: ${{ secrets.TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP: ${{ secrets.PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT_BACKUP }} + PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_V1_WALLET_CONTRACT_DEPLOYMENT }} + PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT: ${{ secrets.PRIVATE_KEY_FOR_BATCHER_CONTRACT_DEPLOYMENT }} + QUICKNODE_ETH_MAINNET_API_KEY: ${{ secrets.QUICKNODE_ETH_MAINNET_API_KEY }} + QUICKNODE_ETH_HOLESKY_API_KEY: ${{ secrets.QUICKNODE_ETH_HOLESKY_API_KEY }} + QUICKNODE_ARBITRUM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_SEPOLIA_API_KEY }} + QUICKNODE_ARBITRUM_ONE_API_KEY: ${{ secrets.QUICKNODE_ARBITRUM_ONE_API_KEY }} + QUICKNODE_OPTIMISM_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_SEPOLIA_API_KEY }} + QUICKNODE_OPTIMISM_API_KEY: ${{ secrets.QUICKNODE_OPTIMISM_API_KEY }} + QUICKNODE_ZKSYNC_SEPOLIA_API_KEY: ${{ secrets.QUICKNODE_ZKSYNC_SEPOLIA_API_KEY }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ALCHEMY_POLYGON_API_KEY: ${{ secrets.ALCHEMY_POLYGON_API_KEY }} + POLYGONSCAN_API_KEY: ${{ secrets.POLYGONSCAN_API_KEY }} + BSCSCAN_API_KEY: ${{ secrets.BSCSCAN_API_KEY }} + ARBISCAN_API_KEY: ${{ secrets.ARBISCAN_API_KEY }} + OPTIMISTIC_ETHERSCAN_API_KEY: ${{ secrets.OPTIMISTIC_ETHERSCAN_API_KEY }} + ZKSYNC_EXPLORER_API_KEY: ${{ secrets.ZKSYNC_EXPLORER_API_KEY }} + BASESCAN_API_KEY: ${{ secrets.BASESCAN_API_KEY }} + CARTIO_BERA_EXPLORER_API_KEY: ${{ secrets.CARTIO_BERA_EXPLORER_API_KEY }} + BERA_EXPLORER_API_KEY: ${{ secrets.BERA_EXPLORER_API_KEY }} + OAS_EXPLORER_API_KEY: ${{ secrets.OAS_EXPLORER_API_KEY }} + CORE_DAO_TESTNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_TESTNET_EXPLORER_API_KEY }} + CORE_DAO_MAINNET_EXPLORER_API_KEY: ${{ secrets.CORE_DAO_MAINNET_EXPLORER_API_KEY }} + FLARE_EXPLORER_API_KEY: ${{ secrets.FLARE_EXPLORER_API_KEY }} + SONGBIRD_EXPLORER_API_KEY: ${{ secrets.SONGBIRD_EXPLORER_API_KEY }} + XDC_EXPLORER_API_KEY: ${{ secrets.XDC_EXPLORER_API_KEY }} + WEMIX_EXPLORER_API_KEY: ${{ secrets.WEMIX_EXPLORER_API_KEY }} + BERA_RPC_URL: ${{ secrets.BERA_RPC_URL }} + MONAD_EXPLORER_API_KEY: ${{ secrets.MONAD_EXPLORER_API_KEY }} + SOMNIA_EXPLORER_API_KEY: ${{ secrets.SOMNIA_EXPLORER_API_KEY }} + SONEIUM_EXPLORER_API_KEY: ${{ secrets.SONEIUM_EXPLORER_API_KEY }} + WORLD_EXPLORER_API_KEY: ${{ secrets.WORLD_EXPLORER_API_KEY }} + CTC_EXPLORER_API_KEY: ${{ secrets.CTC_EXPLORER_API_KEY }} + PHAROS_EXPLORER_API_KEY: ${{ secrets.PHAROS_EXPLORER_API_KEY }} + HYPEEVM_EXPLORER_API_KEY: ${{ secrets.HYPEEVM_EXPLORER_API_KEY }} + SEIEVM_EXPLORER_API_KEY: ${{ secrets.SEIEVM_EXPLORER_API_KEY }} + KAIA_EXPLORER_API_KEY: ${{ secrets.KAIA_EXPLORER_API_KEY }} + IP_EXPLORER_API_KEY: ${{ secrets.IP_EXPLORER_API_KEY }} \ No newline at end of file diff --git a/package.json b/package.json index bd2f383..7f64fdd 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "deploy-prod": "hardhat run scripts/deploy.ts --network", "deploy-test": "hardhat run scripts/deploy.ts --network", "deploy-batcher": "hardhat run scripts/deployBatcherContract.ts --network", + "change-batcher-gas-limit": "hardhat run scripts/changeBatcherGasLimit.ts --network", "test": "hardhat test", "coverage": "hardhat coverage", "solhint": "./node_modules/.bin/solhint --fix 'contracts/**/*.sol'", diff --git a/scripts/changeBatcherGasLimit.ts b/scripts/changeBatcherGasLimit.ts new file mode 100644 index 0000000..5ad5eb9 --- /dev/null +++ b/scripts/changeBatcherGasLimit.ts @@ -0,0 +1,220 @@ +import hre, { ethers } from 'hardhat'; +import { logger } from '../deployUtils'; + +type TxOverrides = { + gasLimit?: number; + maxFeePerGas?: bigint; + maxPriorityFeePerGas?: bigint; + gasPrice?: bigint; +}; + +async function main() { + logger.step('🔧 Starting Batcher Contract Gas Limit Change 🔧'); + + const batcherContractAddress = process.env.BATCHER_CONTRACT_ADDRESS; + const newGasLimitStr = process.env.NEW_GAS_LIMIT; + + if (!batcherContractAddress) { + throw new Error( + 'BATCHER_CONTRACT_ADDRESS environment variable is required' + ); + } + + if (!newGasLimitStr) { + throw new Error('NEW_GAS_LIMIT environment variable is required'); + } + + const newGasLimit = BigInt(newGasLimitStr); + + logger.step('1. Setting up signer and network information...'); + + const signers = await ethers.getSigners(); + if (signers.length < 1) { + throw new Error('No signers available'); + } + + const desiredIndex = process.env.BATCHER_DEPLOYER_INDEX + ? Number(process.env.BATCHER_DEPLOYER_INDEX) + : 2; + let batcherOwner = signers[desiredIndex] || signers[signers.length - 1]; + + const signerInfos = await Promise.all( + signers.map(async (s, i) => { + const addr = await s.getAddress(); + const bal = await ethers.provider.getBalance(addr); + return { index: i, addr, balance: bal }; + }) + ); + + if ( + !batcherOwner || + (await ethers.provider.getBalance(await batcherOwner.getAddress())) === 0n + ) { + const funded = signerInfos + .filter((x) => x.balance > 0n) + .sort((a, b) => (b.balance > a.balance ? 1 : -1))[0]; + if (!funded) { + logger.error('No funded signer available for transaction. Aborting.'); + throw new Error('No funded signer'); + } + batcherOwner = signers[funded.index]; + logger.warn( + `Selected alternative funded signer index=${funded.index} address=${ + funded.addr + } (balance=${ethers.formatEther(funded.balance)})` + ); + } + + const address = await batcherOwner.getAddress(); + const { chainId } = await ethers.provider.getNetwork(); + + logger.info(`Network: ${hre.network.name} (Chain ID: ${chainId})`); + logger.info(`Owner Address: ${address}`); + logger.info(`Batcher Contract Address: ${batcherContractAddress}`); + logger.info(`New Gas Limit: ${newGasLimit.toString()}`); + + logger.step('2. Connecting to Batcher contract...'); + + const batcherAbi = [ + 'function changeTransferGasLimit(uint256 newTransferGasLimit) external', + 'function transferGasLimit() external view returns (uint256)', + 'function owner() external view returns (address)' + ]; + + const batcherContract = new ethers.Contract( + batcherContractAddress, + batcherAbi, + batcherOwner + ); + + logger.step('3. Verifying contract ownership...'); + + try { + const currentOwner = await batcherContract.owner(); + logger.info(`Contract Owner: ${currentOwner}`); + + if (currentOwner.toLowerCase() !== address.toLowerCase()) { + throw new Error( + `Signer ${address} is not the owner of the contract. Owner is ${currentOwner}` + ); + } + + const currentGasLimit = await batcherContract.transferGasLimit(); + logger.info(`Current Gas Limit: ${currentGasLimit.toString()}`); + } catch (error) { + logger.error(`Failed to verify contract details:, ${error}`); + throw error; + } + + logger.step('4. Configuring gas parameters for the transaction...'); + + const eip1559Chains = [10143, 480, 4801, 1946, 1868, 1114, 1112, 1111, 50312]; + const feeData = await ethers.provider.getFeeData(); + let gasOverrides: TxOverrides | undefined = undefined; + + if (Number(chainId) === 50312 || Number(chainId) === 5031) { + const latestBlock = await ethers.provider.getBlock('latest'); + const has1559 = + feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null; + + if (has1559) { + const minPriority = 1_000_000_000n; // 1 gwei + const priority = + (feeData.maxPriorityFeePerGas ?? 0n) > 0n + ? (feeData.maxPriorityFeePerGas as bigint) + : minPriority; + const base = feeData.maxFeePerGas ?? 0n; + const maxFee = base > priority * 2n ? base : priority * 2n; + gasOverrides = { maxFeePerGas: maxFee, maxPriorityFeePerGas: priority }; + logger.info( + `EIP-1559 params (Somnia): maxFeePerGas=${String( + maxFee + )}, maxPriorityFeePerGas=${String(priority)}` + ); + } else { + const fallbackGasPrice = feeData.gasPrice ?? 6_000_000_000n; // 6 gwei fallback + gasOverrides = { gasPrice: fallbackGasPrice }; + logger.info( + `Legacy gasPrice (Somnia): gasPrice=${String(fallbackGasPrice)}` + ); + } + + const est = await batcherContract.changeTransferGasLimit.estimateGas( + newGasLimit, + ); + + const estWithBuffer = (est * 120n) / 100n; // +20% + let chosenGasLimit = Number(estWithBuffer); + if (latestBlock?.gasLimit) { + const blockLimit = Number(latestBlock.gasLimit); + chosenGasLimit = Math.min(chosenGasLimit, Math.floor(blockLimit * 0.95)); + } + gasOverrides.gasLimit = chosenGasLimit; + logger.info( + `Estimated gas: ${est.toString()}, using gasLimit=${chosenGasLimit}` + ); + } else if (eip1559Chains.includes(Number(chainId))) { + gasOverrides = { + maxFeePerGas: feeData.maxFeePerGas ?? feeData.gasPrice ?? undefined, + maxPriorityFeePerGas: + feeData.maxPriorityFeePerGas ?? feeData.gasPrice ?? undefined + }; + logger.info( + `Gas params set: maxFeePerGas=${String( + gasOverrides.maxFeePerGas ?? '' + )}, maxPriorityFeePerGas=${String( + gasOverrides.maxPriorityFeePerGas ?? '' + )}, gasLimit=` + ); + } else { + logger.info(`Using default gas parameters for this network.`); + } + + logger.step('5. Calling changeTransferGasLimit function...'); + + try { + let tx; + if (gasOverrides) { + tx = await batcherContract.changeTransferGasLimit( + newGasLimit, + gasOverrides + ); + } else { + tx = await batcherContract.changeTransferGasLimit(newGasLimit); + } + + logger.info(`Transaction submitted: ${tx.hash}`); + logger.info('Waiting for transaction confirmation...'); + + const receipt = await tx.wait(); + + if (receipt.status === 1) { + logger.success('✅ Transaction confirmed successfully!'); + logger.info(` - Block Number: ${receipt.blockNumber}`); + logger.info(` - Gas Used: ${receipt.gasUsed.toString()}`); + } else { + throw new Error('Transaction failed'); + } + + logger.step('6. Verifying the gas limit change...'); + + const updatedGasLimit = await batcherContract.transferGasLimit(); + logger.info(`Updated Gas Limit: ${updatedGasLimit.toString()}`); + + if (updatedGasLimit.toString() === newGasLimit.toString()) { + logger.success('🎉 Gas limit successfully updated! 🎉'); + } else { + logger.error( + `Gas limit verification failed. Expected: ${newGasLimit.toString()}, Actual: ${updatedGasLimit.toString()}` + ); + } + } catch (error) { + logger.error(`Failed to change gas limit:', ${error}`); + throw error; + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +});